PHP รันกระบวนการพื้นหลัง


259

ฉันต้องดำเนินการคัดลอกไดเรกทอรีตามการกระทำของผู้ใช้ แต่ไดเรกทอรีมีขนาดค่อนข้างใหญ่ดังนั้นฉันจึงอยากจะสามารถดำเนินการดังกล่าวได้โดยที่ผู้ใช้ไม่ได้ตระหนักถึงเวลาที่ใช้ในการทำสำเนาให้เสร็จ

ข้อเสนอแนะใด ๆ ที่จะได้รับการชื่นชมมาก


3
stackoverflow.com/questions/5367261/นี้อธิบายวิธีการทำสิ่งนี้ภายใต้หน้าต่าง
shealtiel

คำตอบ:


365

สมมติว่านี่ทำงานบนเครื่องลีนุกซ์ฉันก็มักจะจัดการแบบนี้เสมอ:

exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));

การเปิดตัวนี้คำสั่ง$cmdเปลี่ยนเส้นทางออกคำสั่งไป$outputfileและเขียนกระบวนการ id $pidfileไป

ซึ่งช่วยให้คุณตรวจสอบกระบวนการที่กำลังทำอยู่ได้อย่างง่ายดายและถ้ามันยังทำงานอยู่

function isRunning($pid){
    try{
        $result = shell_exec(sprintf("ps %d", $pid));
        if( count(preg_split("/\n/", $result)) > 2){
            return true;
        }
    }catch(Exception $e){}

    return false;
}

2
การตั้งค่า sudo จะทำงานโดยไม่ต้องใส่รหัสผ่านหรือไม่? คำสั่งใด ๆ ที่ต้องการอินพุตผู้ใช้จะไม่ทำงาน
Mark Biek

17
stackoverflow.com/questions/5367261/นี้อธิบายวิธีการทำงานภายใต้หน้าต่าง
shealtiel

13
ฉันใช้สิ่งนี้ แต่อัปเดตมันเล็กน้อยดังนั้นฉันจึงไม่ต้องเขียน PID ลงในไฟล์ ดังนั้นฉันใช้รูปแบบนี้: <code> exec (sprintf ("$ s> $ s 2> & 1 & echo $ 1", $ cmd, $ outputfile), $ pidArr); </code> กระบวนการผลลัพธ์ PID อยู่ใน $ pidArr [0]
Kaiesh

3
@ Kaiesh: ควรเป็น "echo $!"
brunobg

8
Kaiesh สร้างคำผิดขึ้นอีกครั้ง '$' แทนที่จะเป็น '%' รุ่นที่แก้ไข: exec (sprintf ("% s>% s 2> & 1 & echo $!", $ cmd, $ outputfile), $ pidArr)
Yuri Gor

23

เขียนกระบวนการเป็นสคริปต์ฝั่งเซิร์ฟเวอร์ในภาษาใดก็ได้ (php / bash / perl / etc) นั้นมีประโยชน์และเรียกมันจากฟังก์ชั่นการควบคุมกระบวนการในสคริปต์ php ของคุณ

ฟังก์ชั่นอาจตรวจพบว่ามีการใช้มาตรฐาน io เป็นกระแสข้อมูลขาออกหรือไม่และหากเป็นเช่นนั้นจะเป็นการตั้งค่าส่งคืนถ้าไม่สิ้นสุด

proc_close( proc_open( "./command --foo=1 &", array(), $foo ) );

ฉันทดสอบสิ่งนี้อย่างรวดเร็วจากบรรทัดคำสั่งโดยใช้ "sleep 25s" เป็นคำสั่งและทำงานเหมือนเครื่องราง

(หาคำตอบได้ที่นี่ )


3
นี่เป็นวิธีเดียวที่ฉันจะทำให้ Centos ทำงานได้ ขอบคุณ!
Deac Karns

ขอบคุณมาก! มีความไม่เข้ากันของ PHP และ BASH หากคุณใช้ระบบที่ใหม่กว่าจะไม่สามารถเปิดใช้งานในพื้นหลังโดยใช้ระบบ () หรือ exec () ตัวอธิบายไฟล์ดูเหมือนจะติดแม้ว่าคุณจะเปลี่ยนเส้นทาง วิธีการของคุณทำงาน อีกทางเลือกหนึ่งคือการใช้ไบนารีทุบตีเก่าสำหรับ PHP แต่ที่ถั่ว
จอห์น

22

คุณอาจต้องการลองผนวกสิ่งนี้ลงในคำสั่งของคุณ

>/dev/null 2>/dev/null &

เช่น.

shell_exec('service named reload >/dev/null 2>/dev/null &');

ฉันหมายถึง> / dev / null 2> / dev / null &
wlf

มันใช้งานได้สำหรับฉันและฉันจะใช้ makefile ทำบางสิ่งที่ไซต์เซิร์ฟเวอร์ขอขอบคุณ! (เช่นทำให้ทำการรีสตาร์ท)
Chu-Siang Lai

นี่ง่ายกว่าคำตอบที่ยอมรับ! (ตราบใดที่คุณไม่ต้องการอะไรที่ซับซ้อนมากขึ้นเช่นการตรวจสอบถ้ากระบวนการจะยังคงทำงาน.)
ฌอนบีน

18

ฉันต้องการเพิ่มตัวอย่างง่ายๆสำหรับการทดสอบฟังก์ชันการทำงานนี้บน Windows:

สร้างสองไฟล์ต่อไปนี้และบันทึกลงในสารบบเว็บ:

foreground.php:

<?php

ini_set("display_errors",1);
error_reporting(E_ALL);

echo "<pre>loading page</pre>";

function run_background_process()
{
    file_put_contents("testprocesses.php","foreground start time = " . time() . "\n");
    echo "<pre>  foreground start time = " . time() . "</pre>";

    // output from the command must be redirected to a file or another output stream 
    // http://ca.php.net/manual/en/function.exec.php

    exec("php background.php > testoutput.php 2>&1 & echo $!", $output);

    echo "<pre>  foreground end time = " . time() . "</pre>";
    file_put_contents("testprocesses.php","foreground end time = " . time() . "\n", FILE_APPEND);
    return $output;
}

echo "<pre>calling run_background_process</pre>";

$output = run_background_process();

echo "<pre>output = "; print_r($output); echo "</pre>";
echo "<pre>end of page</pre>";
?>

background.php:

<?
file_put_contents("testprocesses.php","background start time = " . time() . "\n", FILE_APPEND);
sleep(10);
file_put_contents("testprocesses.php","background end time = " . time() . "\n", FILE_APPEND);
?>

ให้สิทธิ์ IUSR ในการเขียนไปยังไดเรกทอรีที่คุณสร้างไฟล์ด้านบน

ให้สิทธิ์ IUSR ในการอ่านและดำเนินการ C: \ Windows \ System32 \ cmd.exe

กดปุ่ม foreground.php จากเว็บเบราว์เซอร์

ควรแสดงผลต่อไปนี้ไปยังเบราว์เซอร์ที่มีการประทับเวลาปัจจุบันและทรัพยากรท้องถิ่น # ในอาร์เรย์ผลลัพธ์:

loading page
calling run_background_process
  foreground start time = 1266003600
  foreground end time = 1266003600
output = Array
(
    [0] => 15010
)
end of page

คุณควรเห็น testoutput.php ในไดเรกทอรีเดียวกันกับไฟล์ที่บันทึกไว้ข้างต้นและควรจะว่างเปล่า

คุณควรเห็น testprocesses.php ในไดเรกทอรีเดียวกันกับไฟล์ข้างต้นได้รับการบันทึกและควรมีข้อความต่อไปนี้ที่มีเวลาปัจจุบัน

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610

10

หากคุณต้องการทำอะไรบางอย่างในพื้นหลังโดยไม่ต้องมีหน้า PHP รอจนเสร็จคุณสามารถใช้สคริปต์ PHP (พื้นหลัง) อื่นที่ "เรียกใช้" ด้วยคำสั่ง wget พื้นหลังสคริปต์ PHP นี้จะถูกดำเนินการด้วยสิทธิ์แน่นอนเป็นสคริปต์ PHP อื่น ๆ ในระบบของคุณ

นี่คือตัวอย่างบน Windows โดยใช้ wget จากแพ็คเกจ gnuwin32

รหัสพื้นหลัง (ไฟล์ test-proc-bg.php) เป็น exmple ...

sleep(5);   // some delay
file_put_contents('test.txt', date('Y-m-d/H:i:s.u')); // writes time in a file

สคริปต์เบื้องหน้าสคริปต์ที่เรียกใช้ ...

$proc_command = "wget.exe http://localhost/test-proc-bg.php -q -O - -b";
$proc = popen($proc_command, "r");
pclose($proc);

คุณต้องใช้ popen / pclose เพื่อให้มันทำงานได้อย่างถูกต้อง

ตัวเลือก wget:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background

7

นี่คือฟังก์ชั่นเพื่อเปิดตัวกระบวนการพื้นหลังใน PHP ในที่สุดก็สร้างหนึ่งที่ใช้งานได้จริงบน Windows เช่นกันหลังจากการอ่านและการทดสอบแนวทางและพารามิเตอร์ต่าง ๆ มากมาย

function LaunchBackgroundProcess($command){
  // Run command Asynchroniously (in a separate thread)
  if(PHP_OS=='WINNT' || PHP_OS=='WIN32' || PHP_OS=='Windows'){
    // Windows
    $command = 'start "" '. $command;
  } else {
    // Linux/UNIX
    $command = $command .' /dev/null &';
  }
  $handle = popen($command, 'r');
  if($handle!==false){
    pclose($handle);
    return true;
  } else {
    return false;
  }
}

หมายเหตุ 1: บน windows ห้ามใช้/Bพารามิเตอร์ตามที่แนะนำที่อื่น มันบังคับให้กระบวนการทำงานหน้าต่างคอนโซลเดียวกับstartคำสั่งตัวเองส่งผลให้กระบวนการที่กำลังประมวลผลพร้อมกัน กระบวนการทำงานในหัวข้อที่แยกต่างหาก (ถ่ายทอดสด) /Bไม่ได้ใช้

หมายเหตุ 2: start ""จำเป็นต้องใช้เครื่องหมายคำพูดคู่ที่ว่างเปล่าหลังจากนั้นหากคำสั่งนั้นเป็นพา ธ ที่ยกมา startคำสั่งตีความพารามิเตอร์ที่ยกมาครั้งแรกเป็นชื่อหน้าต่าง


6

ฉันพบรุ่นที่เร็วขึ้นและใช้ง่ายขึ้นเล็กน้อย

shell_exec('screen -dmS $name_of_screen $command'); 

และมันใช้งานได้


@ Alpha.Dev ฉันทดสอบบน linux เท่านั้นฉันไม่ทราบว่าจะใช้ระบบอื่นได้หรือไม่
Morfidon

4

คุณสามารถจัดเรียงเพื่อแยกกระบวนการที่แยกต่างหากจากนั้นเรียกใช้สำเนาของคุณในพื้นหลังได้หรือไม่? เป็นเวลานานแล้วที่ฉันทำ PHP แต่ฟังก์ชั่นpcntl-fork นั้นดูดี


สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม หากต้องการวิจารณ์หรือขอคำชี้แจงจากผู้แต่งโปรดแสดงความคิดเห็นใต้โพสต์ของพวกเขา
Rizier123

13
ขอบคุณ - ความคิดเห็นไม่มีอยู่ใน '08 แต่ฉันจะจำไว้ :) :)
Matt Sheppard

4

ใช้ฟังก์ชันนี้เพื่อเรียกใช้โปรแกรมของคุณเป็นพื้นหลัง มันข้ามแพลตฟอร์มและปรับแต่งได้อย่างเต็มที่

<?php
function startBackgroundProcess(
    $command,
    $stdin = null,
    $redirectStdout = null,
    $redirectStderr = null,
    $cwd = null,
    $env = null,
    $other_options = null
) {
    $descriptorspec = array(
        1 => is_string($redirectStdout) ? array('file', $redirectStdout, 'w') : array('pipe', 'w'),
        2 => is_string($redirectStderr) ? array('file', $redirectStderr, 'w') : array('pipe', 'w'),
    );
    if (is_string($stdin)) {
        $descriptorspec[0] = array('pipe', 'r');
    }
    $proc = proc_open($command, $descriptorspec, $pipes, $cwd, $env, $other_options);
    if (!is_resource($proc)) {
        throw new \Exception("Failed to start background process by command: $command");
    }
    if (is_string($stdin)) {
        fwrite($pipes[0], $stdin);
        fclose($pipes[0]);
    }
    if (!is_string($redirectStdout)) {
        fclose($pipes[1]);
    }
    if (!is_string($redirectStderr)) {
        fclose($pipes[2]);
    }
    return $proc;
}

โปรดทราบว่าหลังจากคำสั่งเริ่มต้นโดยค่าเริ่มต้นฟังก์ชั่นนี้จะปิด stdin และ stdout ของกระบวนการทำงาน คุณสามารถเปลี่ยนเส้นทางผลลัพธ์กระบวนการไปยังไฟล์บางไฟล์ผ่านทาง $ redirectStdout และ $ redirectStderr

หมายเหตุสำหรับผู้ใช้ windows:
คุณไม่สามารถเปลี่ยนเส้นทาง stdout / stderr ไปnulในลักษณะต่อไปนี้:

startBackgroundProcess('ping yandex.com', null, 'nul', 'nul');

อย่างไรก็ตามคุณสามารถทำได้:

startBackgroundProcess('ping yandex.com >nul 2>&1');

หมายเหตุสำหรับผู้ใช้ * nix:

1) ใช้คำสั่ง exec shell หากคุณต้องการรับ PID จริง:

$proc = startBackgroundProcess('exec ping yandex.com -c 15', null, '/dev/null', '/dev/null');
print_r(proc_get_status($proc));

2) ใช้อาร์กิวเมนต์ $ stdin หากคุณต้องการส่งผ่านข้อมูลบางอย่างไปยังอินพุตของโปรแกรมของคุณ:

startBackgroundProcess('cat > input.txt', "Hello world!\n");

3

คุณอาจลองระบบการเข้าคิวเหมือนResque จากนั้นคุณสามารถสร้างงานที่ประมวลผลข้อมูลและส่งคืนอย่างรวดเร็วด้วยอิมเมจ "กำลังประมวลผล" ด้วยวิธีการนี้คุณจะไม่ทราบว่าเมื่อมันเสร็จสิ้น

โซลูชันนี้มีไว้สำหรับแอปพลิเคชันขนาดใหญ่ที่คุณไม่ต้องการให้เครื่องด้านหน้ายกขึ้นอย่างหนักดังนั้นพวกเขาจึงสามารถดำเนินการตามคำขอของผู้ใช้ได้ ดังนั้นมันอาจหรืออาจไม่ทำงานกับข้อมูลทางกายภาพเช่นไฟล์และโฟลเดอร์ แต่สำหรับการประมวลผลตรรกะที่ซับซ้อนมากขึ้นหรืองานอะซิงโครนัสอื่น ๆ (เช่นอีเมลการลงทะเบียนใหม่) เป็นการดีที่มีและปรับขนาดได้มาก


2

วิธีแก้ปัญหาการทำงานสำหรับทั้ง Windows และ Linux ค้นหาข้อมูลเพิ่มเติมเกี่ยวกับหน้า GitHub ของฉัน

function run_process($cmd,$outputFile = '/dev/null', $append = false){
                    $pid=0;
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        $cmd = 'wmic process call create "'.$cmd.'" | find "ProcessId"';
                        $handle = popen("start /B ". $cmd, "r");
                        $read = fread($handle, 200); //Read the output 
                        $pid=substr($read,strpos($read,'=')+1);
                        $pid=substr($pid,0,strpos($pid,';') );
                        $pid = (int)$pid;
                        pclose($handle); //Close
                }else{
                    $pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $cmd, ($append) ? '>>' : '>', $outputFile));
                }
                    return $pid;
            }
            function is_process_running($pid){
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        //tasklist /FI "PID eq 6480"
                    $result = shell_exec('tasklist /FI "PID eq '.$pid.'"' );
                    if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                        return true;
                    }
                }else{
                    $result = shell_exec(sprintf('ps %d 2>&1', $pid));
                    if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) {
                        return true;
                    }
                }
                return false;
            }
            function stop_process($pid){
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                            $result = shell_exec('taskkill /PID '.$pid );
                        if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                            return true;
                        }
                    }else{
                            $result = shell_exec(sprintf('kill %d 2>&1', $pid));
                        if (!preg_match('/No such process/', $result)) {
                            return true;
                        }
                    }
            }

2

ขอบคุณคำตอบนี้ : เครื่องมือที่สมบูรณ์แบบสำหรับการเรียกใช้กระบวนการแบ็คกราวน์คือSymfony Process Componentซึ่งขึ้นอยู่กับproc_*ฟังก์ชั่นการใช้งาน แต่ง่ายกว่ามาก ดูเอกสารประกอบสำหรับข้อมูลเพิ่มเติม


1

แทนที่จะเริ่มต้นกระบวนการพื้นหลังสิ่งที่เกี่ยวกับการสร้างไฟล์ทริกเกอร์และมีตารางเวลาเช่น cron หรือ autosys รันสคริปต์ที่ค้นหาและดำเนินการกับไฟล์ทริกเกอร์เป็นระยะ? ทริกเกอร์อาจมีคำแนะนำหรือคำสั่งแบบ raw (ยังดีกว่าเพียงแค่ทำให้มันเป็นเชลล์สคริปต์)



1

ฉันใช้อย่างหนัก fast_cgi_finish_request()

ใช้ร่วมกับการปิดและ register_shutdown_function ()

$message ='job executed';
$backgroundJob = function() use ($message) {
     //do some work here
    echo $message;
}

จากนั้นลงทะเบียนการปิดนี้เพื่อดำเนินการก่อนที่จะปิด

register_shutdown_function($backgroundJob);

ในที่สุดเมื่อคำตอบถูกส่งไปยังลูกค้าคุณสามารถปิดการเชื่อมต่อกับลูกค้าและทำงานกับกระบวนการ PHP ต่อไป:

fast_cgi_finish_request();

การปิดจะถูกดำเนินการหลังจาก fast_cgi_finish_request

ข้อความ $ จะไม่ปรากฏตลอดเวลา และคุณสามารถลงทะเบียนการปิดได้มากเท่าที่คุณต้องการ แต่ดูแลเกี่ยวกับเวลาดำเนินการสคริปต์ สิ่งนี้จะใช้ได้ก็ต่อเมื่อ PHP ทำงานเป็นโมดูล Fast CGI (ใช่ไหม?!)


0

การเขียนสคริปต์ PHP ไม่เหมือนกับภาษาเดสก์ท็อปอื่น ๆ ที่กำลังพัฒนาภาษา ในภาษาแอปพลิเคชันเดสก์ท็อปเราสามารถตั้ง daemon เธรดเพื่อเรียกใช้กระบวนการพื้นหลัง แต่ใน PHP กระบวนการจะเกิดขึ้นเมื่อผู้ใช้ร้องขอหน้า อย่างไรก็ตามมันเป็นไปได้ที่จะตั้งค่างานพื้นหลังโดยใช้ฟังก์ชั่นงาน cron ของเซิร์ฟเวอร์ที่สคริปต์ PHP ทำงาน


0

สำหรับพวกเราที่ใช้Windowsให้ดูที่:

การอ้างอิง: http://php.net/manual/en/function.exec.php#43917

ฉันก็ปล้ำกับการให้โปรแกรมทำงานในพื้นหลังใน Windows ในขณะที่สคริปต์ทำงานต่อไป วิธีนี้แตกต่างจากโซลูชันอื่น ๆ ที่ช่วยให้คุณสามารถเริ่มโปรแกรมย่อเล็กสุดขยายใหญ่สุดหรือไม่มีหน้าต่างเลย โซลูชันของ llbra @ phpbrasil ทำงานได้ แต่บางครั้งก็สร้างหน้าต่างที่ไม่พึงประสงค์บนเดสก์ท็อปเมื่อคุณต้องการให้ภารกิจซ่อนอยู่

เริ่ม Notepad.exe ย่อเล็กสุดในพื้นหลัง:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("notepad.exe", 7, false); 
?> 

เริ่มต้นคำสั่งเชลล์ที่มองไม่เห็นในพื้นหลัง:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false); 
?> 

เริ่มต้น MSPaint ให้ใหญ่สุดและรอให้คุณปิดก่อนดำเนินการต่อสคริปต์

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("mspaint.exe", 3, true); 
?> 

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการ Run () ให้ไปที่: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

URL ที่แก้ไข:

ไปที่https://technet.microsoft.com/en-us/library/ee156605.aspxแทนเนื่องจากไม่มีลิงค์ด้านบนอีกต่อไป


1
สิ่งที่ต้องทำเพื่อเรียกใช้รหัสนี้? ฉันได้รับFatal error: Class 'COM' not found
Alph.Dev

Ps ลิงก์ไปยัง MSDN นั้นใช้งานไม่ได้
Alph.Dev

@ Alph.Dev: เอกสาร msdn ดูเหมือนว่าถูกลบชั่วคราว มันมีตัวอย่างและคำอธิบายเพิ่มเติมเกี่ยวกับวิธีใช้ WScript.Shell
Jimmy Ilenloa

@ Alph.Dev: ไม่พบคลาส COM นั่นเป็นอีกสิ่งหนึ่งโดยสิ้นเชิง ลองดูที่ google คุณอาจตรวจสอบphp.net/manual/en/com.installation.php
Jimmy Ilenloa

@ Alph.Dev โปรดตรวจสอบtechnet.microsoft.com/en-us/library/ee156605.aspxสำหรับการปรับปรุงข้อมูลในการเรียกใช้ฟังก์ชัน ()
จิมมี่ Ilenloa

-12

ฉันรู้ว่ามันเป็นโพสต์เก่าอายุ 100 ปี แต่อย่างไรก็ตามคิดว่ามันอาจจะมีประโยชน์กับใครบางคน คุณสามารถวางภาพที่มองไม่เห็นที่ใดที่หนึ่งบนหน้าเว็บที่ชี้ไปยัง URL ที่จำเป็นต้องเรียกใช้ในพื้นหลังเช่นนี้:

<img src="run-in-background.php" border="0" alt="" width="1" height="1" />


12
ทางออกที่ไม่ดีจริงๆ หากผู้ใช้จะออกจากหน้านั้นกระบวนการจะถูกขัดจังหวะ
Sergey P. aka azure

3
"ภาพที่ล่องลอย" และลิงค์จะปรากฏในซอร์สโค้ดหน้า
tony gil
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.