swoole_process實現多進程

簡介

swoole_process 是swoole提供的進程管理模塊,用來替代PHP的pcntl擴展。php

首先,確保安裝的swoole版本大於1.7.2:html

$ php --ri swoole

swoole

swoole support => enabled
Version => 1.10.1

實例說明

本例裏待消費的是三個shell命令,會分別建立一個子進程來消費。消費的時候故意sleep了1秒,以便直觀看到效果。 shell

process_t1.php數組

<?php

$start_time = microtime(TRUE);

$cmds = [
    "uname",
    "date",
    "whoami"
];

foreach ($cmds as $cmd) {
    $process = new swoole_process( "my_process", true);

    $process->start();
    $process->write($cmd); //經過管道發數據到子進程。管道是單向的:發出的數據必須由另外一端讀取。不能讀取本身發出去的

    echo $rec = $process->read();//同步阻塞讀取管道數據
}

//子進程建立成功後要執行的函數
function my_process(swoole_process $worker){
    sleep(1);//暫停1s

    $cmd = $worker->read();

    // $return = exec($cmd);//exec只會輸出命令執行結果的最後一行內容,且須要顯式打印輸出

    ob_start();
    passthru($cmd);//執行外部程序而且顯示未經處理的、原始輸出,會直接打印輸出。
    $return = ob_get_clean();
    if(!$return) $return = 'null';
    
    $worker->write($return);//寫入數據到管道
}

//子進程結束必需要執行wait進行回收,不然子進程會變成殭屍進程
while($ret = swoole_process::wait()){// $ret 是個數組 code是進程退出狀態碼,
    $pid = $ret['pid'];
    echo PHP_EOL."Worker Exit, PID=" . $pid . PHP_EOL;
}

$end_time = microtime(TRUE);
echo sprintf("use time:%.3f s\n", $end_time - $start_time);

命令行裏運行:bash

$ php process_t1.php  

Linux
Sat Apr 21 15:29:55 CST 2018
root

Worker Exit, PID=672

Worker Exit, PID=674

Worker Exit, PID=676
use time:3.080 s

你們會以爲很奇怪,爲何開了三個子進程,仍是用了3秒,應該是1秒左右纔對呀。swoole

緣由是父進程讀取子進程返回的數據的時候,是同步阻塞讀取:併發

echo $rec = $process->read();//同步阻塞讀取管道數據

致使的後果就是父進程依次等待每一個進程處理完並返回了內容,才走下一次循環。curl

解決方案1:
使用swoole_event_add將管道加入到事件循環中,變爲異步模式:異步

// echo $rec = $process->read();//同步阻塞讀取管道數據

//使用swoole_event_add將管道加入到事件循環中,變爲異步模式
swoole_event_add($process->pipe, function($pipe) use($process) {
    echo $rec = $process->read();
    
    swoole_event_del($process->pipe);//socket處理完成後,從epoll事件中移除管道
});

執行結果:socket

Worker Exit, PID=686

Worker Exit, PID=687

Worker Exit, PID=688
use time:1.060 s
Linux
Sat Apr 21 15:37:14 CST 2018
root

你們會發現,use time數據並非最後打印出來的。已是異步的了。 實際執行時間1s左右。

解決方案2:
先不獲取子進程返回值,循環結束後統一返回:

foreach ($cmds as $cmd) {
    $process = new swoole_process( "my_process", true);

    $process->start();
    $process->write($cmd); //經過管道發數據到子進程

    $process_arr[] = $process;
}

foreach ($process_arr as $process){
    echo $rec = $process->read();
}

執行結果:

Linux
Sat Apr 21 15:52:24 CST 2018
root

Worker Exit, PID=694

Worker Exit, PID=693

Worker Exit, PID=695
use time:1.061 s

函數原型

swoole_process::__construct(callable $function, $redirect_stdin_stdout = false, $create_pipe = true);

第一個參數是子進程建立成功後要執行的函數。
$redirect_stdin_stdout,重定向子進程的標準輸入和輸出。啓用此選項後,在子進程內輸出內容將不是打印屏幕,而是寫入到主進程管道(例如用echo打印的內容也寫入管道)。讀取鍵盤輸入將變爲從管道中讀取數據。默認爲阻塞讀取。
$create_pipe,是否建立管道,啓用$redirect_stdin_stdout後,此選項將忽略用戶參數,強制爲true。若是子進程內沒有進程間通訊,能夠設置爲 false

注意:swoole_process在最新的1.8.0版本已經禁止在Web環境中使用了,因此也只能支持命令行。這時候若是要作併發,multi-curl是不錯的選擇。

進程常駐後臺

若是跑的服務須要一直常駐後臺,能夠在$process->start();前面加上:

swoole_process::daemon();

服務會在後臺運行。

更多示例

多進程獲取網頁狀態碼

<?php

//獲取多個網頁信息

$urls = [
    'https://www.baidu.com',
    'http://www.52fhy.com',
    'http://www.52fhy.com/1',
    'https://www.52fhy.com',
];

foreach ($urls as $key => $url) {
    $process = new swoole_process(function(swoole_process $worker) use ($url){
        $code = getHttpCode($url);
        $worker->write($code);
    }, true);
    $process->start();

    swoole_event_add($process->pipe, function($pipe) use($process, $url) {
        echo sprintf("%s code: %s\n", $url, $process->read());
        swoole_event_del($pipe);
    });
}

echo "ok.\n";

while($ret = swoole_process::wait()){
    // echo PHP_EOL."Worker Exit, PID=" . $ret['pid'] . PHP_EOL;
}

/**
 * 獲取網頁http code
 */
function getHttpCode($url){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 0);
//        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD");
    curl_setopt($ch, CURLOPT_NOBODY, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //不驗證證書
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //不驗證證書
    curl_setopt ($ch, CURLOPT_TIMEOUT_MS, 1000);//超時時間
    curl_exec($ch);
    $info = curl_getinfo($ch);
    curl_close($ch);

    return (string)$info['http_code'];
}

運行:

$ php process_get.php
ok.
http://www.52fhy.com code: 403
http://www.52fhy.com/1 code: 404
https://www.baidu.com code: 200
https://www.52fhy.com code: 403

使用消息隊列通訊

<?php

//獲取多個網頁信息

$urls = [
    'https://www.baidu.com',
    'http://www.52fhy.com',
    'http://www.52fhy.com/1',
    'https://www.52fhy.com',
];

$process = new swoole_process(function(swoole_process $worker) use($urls) {
    foreach ($urls as $url) {
        $code = getHttpCode($url);
        $worker->push($url.': '.$code);
    }
    $worker->push('exit');
}, false, false); //不建立管道
$process->useQueue(1, 2); //使用消息隊列。消息隊列通訊方式與管道不可共用。消息隊列不支持EventLoop,使用消息隊列後只能使用同步阻塞模式非阻塞
$process->start();

while(1){
    $ret = $process->pop();
    if($ret == 'exit') break;
    echo sprintf("%s\n", $ret);
}

echo "ok.\n";

while($ret = swoole_process::wait()){
    echo PHP_EOL."Worker Exit, PID=" . $ret['pid'] . PHP_EOL;
}

/**
 * 獲取網頁http code
 */
function getHttpCode($url){
   //省略
}

運行:

$ php process_get_queue.php
https://www.baidu.com: 200
http://www.52fhy.com: 403
http://www.52fhy.com/1: 404
https://www.52fhy.com: 403
ok.

Worker Exit, PID=1222

參考

一、Process
https://wiki.swoole.com/wiki/...
二、swoole_process->read
https://wiki.swoole.com/wiki/...

相關文章
相關標籤/搜索