PHP實現多進程

swoole 實現php多進程同步

PHP 本身是一個強領域的語言,主要應用於web開發。
PHP 也可以進行多進程開發,但是使用的第三方擴展。
下面我們演示使用 swoole 實現 PHP多進程,且自定義進程名稱,可啓動及停止。
自定義進程名前綴:tprocess-
主進程名爲:tprocess-master
子進程名爲:tprocess-xxx              xxx 爲數字

停止進程有多種方式,比如 kill -9 強制殺死,但這樣會導致任務在中間中斷,出現髒數據
我們使用信號的方式,通知子進程,在下一個任務時退出。

在使用多進程時,容易出現殭屍進程,爲了避免殭屍進程,我們使用 信號接收子進程退消息,並回收資源。

運行

nohup php -f tproc.php start &

停止,會發送信號給運行的主進程,由主進程通知子進程結束

php -f tproc.php stop

啓動後效果如下:

[jingwu@master test]$ ps -ef | grep tprocess
jingwu   10126  4844  0 00:16 pts/5    00:00:00 tprocess-master
jingwu   10127 10126  0 00:16 pts/5    00:00:00 tprocess-0
jingwu   10128 10126  0 00:16 pts/5    00:00:00 tprocess-1
jingwu   10129 10126  0 00:16 pts/5    00:00:00 tprocess-2
jingwu   10130 10126  0 00:16 pts/5    00:00:00 tprocess-3
jingwu   10131 10126  0 00:16 pts/5    00:00:00 tprocess-4

tproc.php

error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('error_log', '/tmp/tprocess_error.log');

define('PROC_LIMIT', 5);

class ProcConfig {
    static $status = null;
}   

//查找對應的進程,我們直接查找系統 /proc 目錄下的文件
//這樣做是因爲,在生產環境不一定會開放 exec 等函數,有一定的侷限
//查找系統文件還可以避免維護進程ID文件
function proc_find() {
    $baseDir = '/proc';
    $rows = scandir('/proc');
    $procTitles = [];
    foreach($rows as $pid) {
        if(!is_numeric($pid)) continue;
        $cmdlineFile = sprintf("%s/%s/cmdline", $baseDir, $pid);
        if(!file_exists($cmdlineFile)) continue;
        $title = trim(file_get_contents($cmdlineFile));
        if(substr($title, 0, 9) != 'tprocess-') continue;
        $procTitles[$title] = $pid;
    }
    return $procTitles;
}   

//檢查子進程,因異常或任務結束而導致子進程結束
//通過檢查,維持一定數量的子進程
function proc_check() {
    $titles = ['ip' => [], 'domain' => []];
    for($i = 0; $i < PROC_LIMIT; $i++) $titles['ip'][] = ['tprocess-'.$i, $i];

    $procs = proc_find();
    foreach($titles as $key => $items) {
        foreach($items as $row) {
            $title = $row[0];
            $index = $row[1];
            if(isset($procs[$title])) continue;
            $worker = new swoole_process(function (swoole_process $process) use($index) {proc_child($process, $index);}, true);
            $procid = $worker->start();
        }
    }
}

//守護進程,負責啓動及監控子進程
function proc_master() {
    $procs = proc_find();
    if(isset($procs['tprocess-master'])) {
        print("Dispatch[running]守護進程已經啓動\n");
        exit(0);
    }
    //主進程異常結束,子進程未結束,結束所有子進程
    if(!isset($procs['tprocess-master']) && $procs) {
        foreach($procs as $title => $pid) posix_kill($pid, 9);
    }

    //設置主進程名
    swoole_set_process_name('tprocess-master');
}    

//worker進程,負責處理數據
function proc_child(swoole_process $process, $index) {
    $title = 'tprocess-'.$index;
    $process->name($title);
    while(1) {
        if(ProcConfig::$status->get()) exit(1);
        sleep(5);
    }
}

$action = isset($argv[1]) ? $argv[1] : '';
if($action == "start") {
    //進程狀態位,用於標識是否可以退出
    ProcConfig::$status = new swoole_atomic(0);

    proc_master();
    print("started worker\n");
    sleep(2);
    proc_check();

    //註冊子進程退出監聽函數,避免殭屍進程
    swoole_process::signal(SIGCHLD, function($sig) {
        while ($ret =  swoole_process::wait(false)) {
            file_put_contents('/tmp/tprocess_defunct.log', "child process killed: {$sig}-{$ret['pid']}\n", FILE_APPEND);
            proc_check();
        }
    });

    //主進程監聽 SIGUSR2 信號,並設置進程狀態位,通知子進程結束
    swoole_process::signal(SIGUSR2, function($sig) {
        ProcConfig::$status->add(1);
        print("receive SIGUSR2 to stop worker\n");
        exit(0);
    });

}else if($action == "stop") {
    $procs = proc_find();
    if(isset($procs['tprocess-master'])) {
        posix_kill($procs['tprocess-master'], SIGUSR2);
    }
    print("stop disptch worker is ok\n");
    exit(0);
} else {
    echo <<< EOF
 cmd [action]

 action list:
     start
     stop

 eg:
     php -f ./tproc.php start
     php -f ./tproc.php stop

EOF;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章