簡介
swoole_process 是swoole提供的進程管理模塊,用來替代PHP的pcntl擴展。
首先,確保安裝的swoole版本大於1.7.2:
$ php --ri swoole
swoole
swoole support => enabled
Version => 1.10.1
注意:swoole_process在最新的1.8.0版本已經禁止在Web環境中使用了,所以也只能支持命令行。這時候如果要做並發,multi-curl是不錯的選擇。https://group.swoole.com/question/106198
實例說明
本例里待消費的是三個shell命令,會分別創建一個子進程來消費。消費的時候故意sleep了1秒,以便直觀看到效果。
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);
命令行里運行:
$ 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秒左右才對呀。
原因是父進程讀取子進程返回的數據的時候,是同步阻塞讀取:
echo $rec = $process->read();//同步阻塞讀取管道數據
導致的后果就是父進程依次等待每個進程處理完並返回了內容,才走下一次循環。
解決方案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事件中移除管道
});
執行結果:
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
進程常駐后台
如果跑的服務需要一直常駐后台,可以在$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
參考
1、Process
https://wiki.swoole.com/wiki/page/p-process.html
2、swoole_process->read
https://wiki.swoole.com/wiki/page/217.html