Swoole的多進程模塊
介紹
Swoole是有自己的一個進程管理模塊,用來替代PHP的pcntl擴展,需要注意Process進程在系統是非常昂貴的資源,創建進程消耗很大,另外創建的進程過多會導致進程切換開銷大幅上升。
爲什麼不使用pcntl
- pcntl沒有提供進程間通信的功能
- pcntl不支持重定向標準輸入和輸出
- pcntl只提供了fork這樣原始的接口,容易使用錯誤
Swoole是怎麼解決的
- swoole_process提供了基於unixsock的進程間通信,使用很簡單隻需調用write/read或者push/pop即可
- swoole_process支持重定向標準輸入和輸出,在子進程內echo不會打印屏幕,而是寫入管道,讀鍵盤輸入可以重定向爲管道讀取數據
- swoole_process提供了exec接口,創建的進程可以執行其他程序,與原PHP父進程之間可以方便的通信
創建進程
函數原型:
Swoole\Process::__construct(callable $function, $redirect_stdin_stdout = false, $create_pipe = true)
-
$function
,子進程創建成功後要執行的函數,底層會自動將函數保存到對象的callback屬性上。如果希望更改執行的函數,可賦值新的函數到對象的callback屬性 -
$redirect_stdin_stdout
,重定向子進程的標準輸入和輸出。啓用此選項後,在子進程內輸出內容將不是打印屏幕,而是寫入到主進程管道。讀取鍵盤輸入將變爲從管道中讀取數據。默認爲阻塞讀取。 - $create_pipe,是否創建管道,啓用
$redirect_stdin_stdout
後,此選項將忽略用戶參數,強制爲true。如果子進程內沒有進程間通信,可以設置爲 false。
swoole創建多進程很簡單:new Swoole\Process('callback_function')
就可以了,比如我要同時創建6個進程,就for 循環6次就可以了。
假設前臺給後臺三組任務要求後臺去執行,每個任務大概需要執行一秒的時間,我們利用多進程的形式去實現,讓時間能夠縮短。
for ($i = 0; $i < 6; $i++) {#創建了3個子進程
$process = new Swoole\Process(function ($process) {
sleep(1);
echo PHP_EOL . posix_getpid() . PHP_EOL;#獲取子進程PID
}, false, true);
$process->start();
進程間的通訊
如果是非常簡單的多進程執行任務,那麼進程間就不需要通訊了,實際情況下,很多業務是需要通訊的,比如,發郵件,如果自進程發送失敗了,那麼是要通知主進程的等等。
swoole_process
進程間支持2種通信方式:
- 管道pipe
- 消息隊列
管道通訊
半雙工: 數據單向流動, 一端只讀, 一端只寫。
同步 vs 異步: 默認爲同步阻塞模式, 可以使用 swoole_event_add()
添加管道到 swoole 的 event loop
中, 實現異步IO
管道通信是swoole_process
默認的一種通信方式。當然我們也可以在實例化的時候通過參數來設定:
$process = new Swoole\Process('callback_function', false, true);
如果我們打印$process
會發現,每次創建一個進程後,就會隨之創建一個管道,主進程想和哪一個進程通信,就向那個進程的管道寫入/讀取數據。
管道有2個方法,分別來寫入數據,和讀取數據。
$process->write('數據');#寫入數據
$process->read()#讀取數據
管道通訊方式一:
$worker = [];
for ($i = 0; $i < 3; $i++) {
$process = new Swoole\Process(function ($process) {
var_dump('子進程:' . $process->read());
sleep(1);
$process->write('子進程數據');
echo PHP_EOL . posix_getpid() . PHP_EOL;
}, false, true);
$pid = $process->start();
$worker[$pid] = $process;//把相應的進程放到同一個數組當中
$process->write('主進程數據');
// var_dump($process->read());//同步阻塞
}
foreach ($worker as $w) {
var_dump('主進程:' . $w->read());
}
管道通訊方式二:
for ($i = 0; $i < 3; $i++) {
$process = new Swoole\Process(function ($process) {
var_dump('子進程:' . $process->read());
sleep(1);
$process->write('子進程數據');
echo PHP_EOL . posix_getpid() . PHP_EOL;
}, false, true);
$pid = $process->start();
$process->write('主進程數據');
// 異步監聽管道中的數據,讀事件監聽,當管道可讀時觸發
swoole_event_add($process->pipe, function ($pipe) use ($process) {
var_dump('主進程:' . $process->read());
});
// var_dump($process->read());//同步阻塞
}
消息隊列的通訊
消息隊列:
- 一系列保存在內核中的消息鏈表
- 有一個 msgKey, 可以通過此訪問不同的消息隊列
- 有數據大小限制, 默認 8192
- 阻塞 vs 非阻塞: 阻塞模式下 pop()空消息隊列/push()滿消息隊列會阻塞, 非阻塞模式可以直接返回
swoole 中使用消息隊列:
- 通信模式: 默認爲爭搶模式, 無法將消息投遞給指定子進程
- 新建消息隊列後, 主進程就可以使用
- 消息隊列不可和管道一起使用, 也無法使用 swoole event loop
步驟:
-
啓用消息隊列作爲進程間通信:
bool swoole_process->useQueue(int $msgkey = 0, int $mode = 2);
-
投遞數據到消息隊列中:
bool swoole_process->push(string $data);
-
從隊列中提取數據
string swoole_process->pop(int $maxsize = 8192);
案例:
for ($i = 0; $i < 3; $i++) { $process = new Swoole\Process(function ($process) { var_dump('子進程:' . $process->pop()); // $process->push('hello 主進程');#推送到主進程 }); $process->useQueue(1, 2 | swoole_process::IPC_NOWAIT);//啓用消息隊列,爭搶模式,非阻塞,可能會被任意一個子進程接收到 $pid = $process->start(); $process->push('hello 子進程');#推送到子進程,不能當做管道使用 // echo '主進程消息:' . $process->pop() . PHP_EOL; }
謝謝觀賞
謝謝大家耐心觀看,希望對您有所幫助,也希望大家提供下不同的意見,找到更有效的方式來完成,共同學習,謝謝!