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;
}