Swoole從入門到入土(22)——多進程[Process]

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

 

進程類的介紹到這裏就結束了,下一節咱們將進入進程池話題:)

 

 

---------------------------  我是可愛的分割線  ----------------------------

最後博主借地宣傳一下,漳州編程小組招新了,這是一個面向漳州青少年信息學/軟件設計的學習小組,有意向的同窗點擊連接,聯繫我吧。

相關文章
相關標籤/搜索