[toc]
## 進程管理
### 官網實例了解進程api
根據官方實例加上注釋
* 子進程異常退出時,自動重啟
* 主進程異常退出時,子進程會繼續執行,完成所有任務后退出
~~~
class Process
{
// 主進程的pid
public $mpid = 0;
//
public $works = [];
// 創建多少個子進程
public $max_precess = 1;
public $new_index = 0;
public function __construct()
{
try {
// 給當前主進程命名
swoole_set_process_name(sprintf('php-ps:%s' , 'master'));
// 獲得當前主進程的PID
$this->mpid = posix_getpid();
// 創建子進程并運行
$this->run();
// 運行結束后回收并重啟
$this->processWait();
} catch (\Exception $e) {
die('ALL ERROR: ' . $e->getMessage());
}
}
public function run()
{
for ($i = 0;$i < $this->max_precess;$i ++) {
$this->CreateProcess();
}
}
public function CreateProcess($index = null)
{
// 創建子進程,用于在檢查主進程是否還活著
$process = new \swoole_process(
function (\swoole_process $worker) use ($index){
// 設置進程名稱
if (is_null($index)) {
$index = $this->new_index;
$this->new_index ++;
}
swoole_set_process_name(sprintf('php-ps:%s' , $index));
// 120秒后重啟子進程
for ($j = 0;$j < 120;$j ++) {
$this->checkMpid($worker);
echo "msg: {$j}\n";
sleep(1);
}
} , false , false
);
$pid = $process->start();
$this->works[$index] = $pid;
return $pid;
}
public function checkMpid(&$worker)
{
// 檢查主進程是否存在
if (!\swoole_process::kill($this->mpid , 0)) {
// 如果不存在的話就退出子進程
$worker->exit();
// 這句提示,實際是看不到的.需要寫到日志中
echo "Master process exited, I [{$worker['pid']}] also quit\n";
}
}
public function rebootProcess($ret)
{
$pid = $ret['pid'];
$index = array_search($pid , $this->works);
if ($index !== false) {
$index = intval($index);
// 名稱設置為一樣
$new_pid = $this->CreateProcess($index);
echo "rebootProcess: {$index}={$new_pid} Done\n";
return;
}
throw new \Exception('rebootProcess Error: no pid');
}
public function processWait()
{
while (1) {
// 如果子進程存在
if (count($this->works)) {
// 等待其結束后回收
$ret = \swoole_process::wait();
if ($ret) {
// 回收成功后重啟子進程
$this->rebootProcess($ret);
}
} else {
break;
}
}
}
}
~~~
使用`ps aft | grep php`查看進程關系

### 父子進程通過管道通信
~~~
// 第二參數設為true,可以使用write和read來通過管道使父子進程通信
$process = new \swoole_process(
function (\swoole_process $process){
$process->write('Hello');
} , true
);
$process->start();
usleep(100);
// 輸出 Hello
echo $process->read();
~~~
### 調用外部程序
~~~
$process = new \Swoole\Process(
function (\Swoole\Process $childProcess){
// 1. 必須寫絕對路徑
// 2. 參數必須分開放到數組中
$childProcess->exec(
'/usr/local/bin/php' , [
'/var/www/project/yii-best-practice/cli/yii' ,
't/index' ,
'-m=123' ,
'abc' ,
'xyz'
]);
// 3. 執行shell命令,略有區別。不過一般使用協程co:exec()
$childProcess->exec('/bin/sh' , ['-c' , "cp -rf /data/test/* /tmp/test/"]);
}
);
$process->start();
~~~
### 在SwooleServer中添加用戶自定義進程
~~~
$server = new \Swoole\Server('127.0.0.1', 9501);
/**
* 自定義用戶進程實現廣播功能
* 循環接收管道消息,并發給服務器的所有連接
*/
$process = new \Swoole\Process(function($process) use ($server) {
// 用戶進程內應當進行while(true)或EventLoop循環,否則用戶進程會不停地退出重啟
while (true) {
$msg = $process->read();
// 創建的子進程可以調用$server對象提供的各個方法和屬性
foreach($server->connections as $conn) {
$server->send($conn, $msg);
}
}
});
// 在swooleServer中新增進程時,子進程不需要start
// 在Server啟動時會自動創建進程,并執行指定的子進程函數
$server->addProcess($process);
$server->on('receive', function ($serv, $fd, $reactor_id, $data) use ($process) {
//群發收到的消息
$process->write($data);
});
$server->start();
~~~
可以參考easyswoole的編碼方式:http://www.easyswoole.com/Manual/3.x/Cn/_book/BaseUsage/process.html。 把復雜的業務邏輯寫進類里面,然后實例出來調用`getProcess()`
### 進程池
~~~
// 設置10個工作進程
$workerNum = 10;
$pool = new \Swoole\Process\Pool($workerNum);
// 配置事件回調
$pool->on("WorkerStart", function ($pool, $workerId) {
// 得到Process對象,可以使用Process對象的方法
$process = $pool->getProcess();
$process->exec("/bin/sh", ['-c', 'ls -l']);
});
$pool->on("WorkerStop", function ($pool, $workerId) {
echo "Worker#{$workerId} is stopped\n";
});
// 啟動工作進程
$pool->start();
~~~
>[danger] 這邊測試下來,`onWorkerStart`事件一直重復觸發,按道理是只會在進程啟動的時候執行一次。原因未知。有可能是單核虛擬機的問題。
### 進程信號異步監聽
[Linux信號列表](https://wiki.swoole.com/wiki/page/p-LinuxSignal.html)
~~~
// 監聽SIGTERM信號(停止)
\Swoole\Process::signal(SIGTERM, function($signo) {
echo "shutdown.";
});
~~~

查到當前的進程pid為19093, 然后 `kill -9 19093`, 殺掉之后就輸出`shutdown.`
## 進程隔離與內存共享
進程和進程之間是隔離的。
* 不同的進程中PHP變量不是共享,即使是全局變量,在A進程內修改了它的值,在B進程內是無效的
* 如果需要在不同的Worker進程內共享數據,可以用`Redis`、`MySQL`、`文件`、`Swoole\Table`、`APCu`、`shmget`等工具實現
* 不同進程的文件句柄是隔離的,所以在A進程創建的Socket連接或打開的文件,在B進程內是無效,即使是將它的fd發送到B進程也是不可用的
~~~
$server = new \Swoole\Http\Server('0.0.0.0', 9500);
// 4個進程
$server->set([ 'worker_num' => 4]);
$i = 1;
$server->on('Request', function ($request, $response) {
global $i;
$response->end($i++);
});
$server->start();
~~~
用兩個瀏覽器訪問上面的`HttpServer`,會發現輸出的$i都不一樣。如果是worker設置為1的話就一樣。因為`$i`變量雖然是全局變量(`global`),但由于進程隔離的原因。假設有`4`個工作進程,在`進程1`中進行`$i++`,實際上只有`進程1`中的`$i`變成`2`了,其他另外`3`個進程內`$i`變量的值還是`1`。
使用`Swoole\Table`來做到內存共享(其實更建議`redis`)
~~~
$server = new \Swoole\Http\Server('0.0.0.0', 9500);
// 4個進程
$server->set([ 'worker_num' => 4]);
// 參數指定表格的最大行數,必須為2的n次方,如果小于1024則默認成1024,即1024是最小值
// Table基于行鎖,所以單次set/get/del在多線程/多進程的環境下是安全的
// set/get/del等方法是原子操作,用戶代碼中不需要擔心數據加鎖和同步的問題
$table = new \Swoole\Table(8);
// 字符串類型必須指定第三參數,如果是整型的話,注意溢出
$table->column('i',$table::TYPE_INT);
$table->create();
$table->set('table',['i' => 1]);
$server->on('Request', function ($request, $response) use($table) {
$table->incr('table','i');
$response->end($table->get('table','i'));
});
$server->start();
~~~
更多參考: https://wiki.swoole.com/wiki/page/p-table.html
>[info] 進程在系統是非常昂貴的資源,創建進程消耗很大,要謹慎使用。如果要創建的子進程業務是要長期執行的,比如“監控文件文件變化”,可以使用子進程。其它短期任務,能用協程盡量用協程。