使用 proc open() 生成非阻塞程序
除非你安裝 pthread
等擴充套件,否則 PHP 不支援併發執行程式碼。這有時可以通過使用 proc_open()
和 stream_set_blocking()
並非同步讀取它們的輸出來繞過。
如果我們將程式碼分成更小的塊,我們可以將它作為多個 suprocesses 執行。然後使用 stream_set_blocking()
函式,我們可以使每個子程序也非阻塞。這意味著我們可以生成多個子程序,然後在迴圈中檢查它們的輸出(類似於偶數迴圈)並等待所有子程序完成。
作為一個例子,我們可以有一個只執行迴圈的小子程序,並且在每次迭代中隨機休眠 100-1000ms(注意,一個子程序的延遲總是相同的)。
<?php
// subprocess.php
$name = $argv[1];
$delay = rand(1, 10) * 100;
printf("$name delay: ${delay}ms\n");
for ($i = 0; $i < 5; $i++) {
usleep($delay * 1000);
printf("$name: $i\n");
}
然後主程序將產生子程序並讀取它們的輸出。我們可以將它拆分成更小的塊:
- 使用
proc_open()
生成子程序 。 - 使用
stream_set_blocking()
使每個子程序無阻塞 。 - 執行迴圈,直到所有子程序都使用
proc_get_status()
完成。 - 正確關閉檔案處理與輸出管道使用每個子
fclose()
和關閉的過程與處理proc_close()
。
<?php
// non-blocking-proc_open.php
// File descriptors for each subprocess.
$descriptors = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
];
$pipes = [];
$processes = [];
foreach (range(1, 3) as $i) {
// Spawn a subprocess.
$proc = proc_open('php subprocess.php proc' . $i, $descriptors, $procPipes);
$processes[$i] = $proc;
// Make the subprocess non-blocking (only output pipe).
stream_set_blocking($procPipes[1], 0);
$pipes[$i] = $procPipes;
}
// Run in a loop until all subprocesses finish.
while (array_filter($processes, function($proc) { return proc_get_status($proc)['running']; })) {
foreach (range(1, 3) as $i) {
usleep(10 * 1000); // 100ms
// Read all available output (unread output is buffered).
$str = fread($pipes[$i][1], 1024);
if ($str) {
printf($str);
}
}
}
// Close all pipes and processes.
foreach (range(1, 3) as $i) {
fclose($pipes[$i][1]);
proc_close($processes[$i]);
}
然後輸出包含來自所有三個子程序的混合,因為我們通過 fread()
讀取它們 (注意,在這種情況下,proc1
比其他兩個更早結束):
$ php non-blocking-proc_open.php
proc1 delay: 200ms
proc2 delay: 1000ms
proc3 delay: 800ms
proc1: 0
proc1: 1
proc1: 2
proc1: 3
proc3: 0
proc1: 4
proc2: 0
proc3: 1
proc2: 1
proc3: 2
proc2: 2
proc3: 3
proc2: 3
proc3: 4
proc2: 4