Swoole中的Process模塊比原生php提供的pcntl模塊,提供了更易用的多進程編程接口。 簡單總結,Process模塊有以下特色:php
· 能夠方便的實現進程間通信
· 支持重定向標準輸入和輸出,在子進程內 echo 不會打印屏幕,而是寫入管道,讀鍵盤輸入能夠重定向爲管道讀取數據
· 提供了 exec 接口,建立的進程能夠執行其餘程序,與原 PHP 父進程之間能夠方便的通訊
· 在協程環境中沒法使用 Process 模塊,可使用 runtime hook+proc_open 實現,參考協程進程管理python
讓咱們先看一下例子:建立 3 個子進程,主進程用 wait 回收進程,主進程異常退出時,子進程會繼續執行,完成全部任務後退出shell
use Swoole\Process; for ($n = 1; $n <= 3; $n++) { $process = new Process(function () use ($n) { echo 'Child #' . getmypid() . " start and sleep {$n}s" . PHP_EOL; sleep($n); echo 'Child #' . getmypid() . ' exit' . PHP_EOL; }); $process->start(); } for ($n = 3; $n--;) { $status = Process::wait(true); echo "Recycled #{$status['pid']}, code={$status['code']}, signal={$status['signal']}" . PHP_EOL; } echo 'Parent #' . getmypid() . ' exit' . PHP_EOL;
關於上面使用的Process成員屬性或函數看不懂?不用着急,如今咱們就一一進行討論。編程
屬性數組
1) pid:子進程的pid服務器
Swoole\Process->pid: int
2) pipe:unixSocket 的文件描述符。swoole
Swoole\Process->pipe;
方法異步
1) __contruct():構造方法socket
Swoole\Process::__construct(callable $function, bool $redirect_stdin_stdout = false, int $pipe_type = SOCK_DGRAM, bool $enable_coroutine = false);
$function:子進程建立成功後要執行的函數【底層會自動將函數保存到對象的 callback 屬性上。若是但願更改執行的函數,可賦值新的函數到對象的 callback 屬性】函數
$redirect_stdin_stdout:重定向子進程的標準輸入和輸出。【啓用此選項後,在子進程內輸出內容將不是打印屏幕,而是寫入到主進程管道。讀取鍵盤輸入將變爲從管道中讀取數據。默認爲阻塞讀取。】
$pipe_type:unixSocket 類型【啓用 $redirect_stdin_stdout 後,此選項將忽略用戶參數,強制爲 1。若是子進程內沒有進程間通訊,能夠設置爲 0】,可選值以下:
$enable_coroutine:在 callback function 中啓用協程,開啓後能夠直接在子進程的函數中使用協程 API。
2) set():設置參數
Swoole\Process->set(array $settings)
可使用 enable_coroutine 來控制是否啓用協程,和構造函數的第四個參數做用一致。
Swoole\Process->set(['enable_coroutine' => true]);
3) start():執行 fork 系統調用,啓動子進程。在 Linux 系統下建立一個進程須要數百微秒時間。
Swoole\Process->start(): int|false
返回值:成功返回子進程的 PID。失敗返回 false。可以使用 swoole_errno 和 swoole_strerror 獲得錯誤碼和錯誤信息。
注意:
子進程會繼承父進程的內存和文件句柄
子進程在啓動時會清除從父進程繼承的 EventLoop、Signal、Timer
4) exportSocket():將 unixSocket 導出爲 Coroutine\Socket 對象,而後利用 Coroutine\socket 對象的方法進程間通信。
Swoole\Process->exportSocket(): Swoole\Coroutine\Socket|false
返回值:成功返回 Coroutine\Socket 對象。進程未建立 unixSocket,操做失敗,返回 false
注意:
屢次調用此方法,返回的對象是同一個;
exportSocket() 導出的 socket 是一個新的 fd,當關閉導出的 socket 時不會影響進程原有的管道。
因爲是 Coroutine\Socket 對象,必須在協程容器中使用,因此 Swoole\Process 構造函數 $enable_coroutine 參數必須爲 true。
一樣的父進程想用 Coroutine\Socket 對象,須要手動 Co\run() 以建立協程容器。
示例:
use Swoole\Process; $proc1 = new Process(function (Process $proc) { $socket = $proc->exportSocket(); echo $socket->recv(); $socket->send("hello master\n"); echo "proc1 stop\n"; }, false, 1, true); $proc1->start(); //父進程建立一個協程容器 Co\run(function() use ($proc1) { $socket = $proc1->exportSocket(); $socket->send("hello pro1\n"); var_dump($socket->recv()); }); Process::wait(true);
注意:默認類型是 SOCK_STREAM,須要處理粘包問題,參考 Coroutine\socket 的 setProtocol() 方法。使用 SOCK_DGRAM 類型進行 IPC 通信,能夠避免處理粘包問題。
示例:
use Swoole\Process; //IPC通信即便是 SOCK_DGRAM 類型的socket也不須要用 sendto / recvfrom 這組函數,send/recv便可。 $proc1 = new Process(function (Process $proc) { $socket = $proc->exportSocket(); while (1) { var_dump($socket->send("hello master\n")); } echo "proc1 stop\n"; }, false, 2, 1);//構造函數pipe type傳爲2 即SOCK_DGRAM $proc1->start(); Co\run(function() use ($proc1) { $socket = $proc1->exportSocket(); Co::sleep(5); var_dump(strlen($socket->recv()));//一次recv只會收到一個"hello master\n"字符串 不會出現多個"hello master\n"字符串 }); Process::wait(true);
5) name():修改進程名稱。此函數是 swoole_set_process_name 的別名。
Swoole\Process->name(string $name): bool
注意:在執行 exec 後,進程名稱會被新的程序從新設置;name 方法應當在 start 以後的子進程回調函數中使用。
6) exec():執行一個外部程序,此函數是 exec 系統調用的封裝。
Swoole\Process->exec(string $execfile, array $args);
$execfile:指定可執行文件的絕對路徑,如 "/usr/bin/python"。必須使用絕對路徑,不然會報文件不存在錯誤。
$args:exec 的參數列表【如 array('test.py', 123),至關於 python test.py 123】
注意:因爲 exec 系統調用會使用指定的程序覆蓋當前程序,子進程須要讀寫標準輸出與父進程進行通訊;若是未指定 redirect_stdin_stdout = true,執行 exec 後子進程與父進程沒法通訊。
示例1:
$process = new Swoole\Process('callback_function', true); $pid = $process->start(); function callback_function(Swoole\Process $worker) { $worker->exec('/usr/local/bin/php', array(__DIR__.'/swoole_server.php')); } Swoole\Process::wait();
示例2:父進程與子進程exec通訊
// exec - 與exec進程進行管道通訊 use Swoole\Process; $process = new Process(function (Process $worker) { $worker->exec('/bin/echo', ['hello']); }, true, 1, true); // 須要啓用標準輸入輸出重定向 $process->start(); Co\run(function() use($process) { $socket = $process->exportSocket(); echo "from exec: " . $socket->recv() . "\n"; });
示例3:執行shell
$worker->exec('/bin/sh', array('-c', "cp -rf /data/test/* /tmp/test/"));
7) close():用於關閉建立的好的 unixSocket。
Swoole\Process->close(int $which): bool
$which:因爲 unixSocket 是全雙工的,指定關閉哪一端【默認爲 0 表示同時關閉讀和寫,1:關閉寫,2 關閉讀】
注意:有一些特殊的狀況 Process 對象沒法釋放,若是持續建立進程會致使鏈接泄漏。調用此函數就能夠直接關閉 unixSocket,釋放資源。
8) exit():退出子進程。
Swoole\Process->exit(int $status = 0);
$status:退出進程的狀態碼【若是爲 0 表示正常結束,會繼續執行清理工做】
注意事項:
清理工做包括:PHP 的 shutdown_function、對象析構(__destruct)、其餘擴展的 RSHUTDOWN 函數
若是 $status 不爲 0,表示異常退出,會當即終止進程,再也不執行相關進程終止的清理工做。
在父進程中,執行 Process::wait 能夠獲得子進程退出的事件和狀態碼。
9) kill():向指定 pid 進程發送信號。
Swoole\Process::kill(int $pid, int $signo = SIGTERM): bool
$pid:進程pid
$signo:發送的信號【$signo=0,能夠檢測進程是否存在,不會發送信號】
10) signal():設置異步信號監聽。
Swoole\Process::signal(int $signo, callable $callback): bool
$signo:信號
$callback:回調函數【$callback 若是爲 null,表示移除信號監聽】
注意:
此方法基於 signalfd 和 EventLoop 是異步 IO,不能用於阻塞的程序中,會致使註冊的監聽回調函數得不到調度;
同步阻塞的程序可使用 pcntl 擴展提供的 pcntl_signal;
若是已設置了此信號的回調函數,從新設置時會覆蓋歷史設置。
在 Swoole\Server 中不能設置某些信號監聽,如 SIGTERM 和 SIGALAM。
若是進程的 EventLoop 中只有信號監聽的事件,沒有其餘事件 (例如 Timer 定時器等),進程會直接退出。
示例1:
Swoole\Process::signal(SIGTERM, function($signo) { echo "shutdown."; });
示例2:
//如下程序不會進入 EventLoop,Swoole\Event::wait() 將當即返回,並退出進程。 Swoole\Process::signal(SIGTERM, function($signo) { echo "shutdown."; }); Swoole\Event::wait();
11) wait():回收結束運行的子進程。
Swoole\Process::wait(bool $blocking = true): array|false
$blocking:指定是否阻塞等待【默認爲阻塞】
返回值:操做成功會返回一個數組包含子進程的 PID、退出狀態碼、被哪一種信號 KILL;失敗返回 false
注:推薦使用協程版本的 Swoole\Coroutine\System::wait()
注意事項:
每一個子進程結束後,父進程必須都要執行一次 wait() 進行回收,不然子進程會變成殭屍進程,會浪費操做系統的進程資源。
若是父進程有其餘任務要作,無法阻塞 wait 在那裏,父進程必須註冊信號 SIGCHLD 對退出的進程執行 wait。
SIGCHILD 信號發生時可能同時有多個子進程退出;必須將 wait() 設置爲非阻塞,循環執行 wait 直到返回 false。
示例:
<?php use Swoole\Process; for($i=1;$i<=3;$i++) { $process=new Process(function() use ($i){ Swoole\Timer::tick(1000, function () { echo "child timer:",getmypid(),PHP_EOL; }); },false,1,true); $process->start(); } Process::signal(SIGCHLD, static function ($sig) { while ($ret = Process::wait(false)) { echo "PID={$ret['pid']}\n"; } }); //這裏很重要,要注意:Timer::tick必須存在(即後面的操做必須是異步操做,否能是同步操做) Swoole\Timer::tick(2000, function () { echo "parent timer\n"; });
12) daemon():使當前進程蛻變爲一個守護進程。
Swoole\Process::daemon(bool $nochdir = true, bool $noclose = true): bool
$nochdir:是否切換當前目錄到根目錄【爲 true 表示不要切換當前目錄到根目錄】
$noclose:是否要關閉標準輸入輸出文件描述符【爲 true 表示不要關閉標準輸入輸出文件描述符】
注意:蛻變爲守護進程時,該進程的 PID 將發生變化,可使用 getmypid() 來獲取當前的 PID
13) alarm():高精度定時器,是操做系統 setitimer 系統調用的封裝,能夠設置微秒級別的定時器。定時器會觸發信號,須要與 Process::signal 或 pcntl_signal 配合使用。
Swoole\Process::alarm(int $time, int $type = 0): bool
$time:定時器間隔時間【若是爲負數表示清除定時器】,單位:微秒
$type:定時器類型
返回值:設置成功返回 true;失敗返回 false,可使用 swoole_errno 獲得錯誤碼。
示例:
Swoole\Process::signal(SIGALRM, function () { static $i = 0; echo "#{$i}\talarm\n"; $i++; if ($i > 20) { Swoole\Process::alarm(-1); } }); //100ms Swoole\Process::alarm(100 * 1000);
注意:alarm 不能和 Timer 同時使用。
14) setAffinity():設置 CPU 親和性,能夠將進程綁定到特定的 CPU 核上。此函數的做用是讓進程只在某幾個 CPU 核上運行,讓出某些 CPU 資源執行更重要的程序。
Swoole\Process::setAffinity(array $cpus): bool
$cpus:綁定 CPU 核 【如 array(0,2,3) 表示綁定 CPU0/CPU2/CPU3】
注意:
- $cpus 內的元素不能超過 CPU 核數;
- CPU-ID 不得超過(CPU 核數 - 1);
- 使用 swoole_cpu_num() 能夠獲得當前服務器的 CPU 核數。
15) setPriority():設置進程、進程組和用戶進程的優先級。
Swoole\Process->setPriority(int $which, int $priority): bool
$witch:決定修改優先級的類型
$priority:優先級。值越小,優先級越高。可選值範圍 [-20,20]
返回值:若是返回 false,可以使用 swoole_errno 和 swoole_strerror 獲得錯誤碼和錯誤信息。
16) getPriority():獲取進程的優先級。
Swoole\Process->getPriority(int $which): int
進程類的介紹到這裏就結束了,下一節咱們將進入進程池話題:)
--------------------------- 我是可愛的分割線 ----------------------------
最後博主借地宣傳一下,漳州編程小組招新了,這是一個面向漳州青少年信息學/軟件設計的學習小組,有意向的同窗點擊連接,聯繫我吧。