Process

1.7.2版本增加了一个进程管理模块,用来替代PHPpcntl

需要注意Process进程在系统是非常昂贵的资源,创建进程消耗很大。另外创建的进程过多会导致进程切换开销大幅上升。可以使用vmstat指令查看操作系统每秒进程切换的次数。

vmstat 1 1000
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 8250028 509872 4061168    0    0    10    13   88   86  1  0 99  0  0
 0  0      0 8249532 509872 4061936    0    0     0     0  451 1108  0  0 100  0  0
 0  0      0 8249532 509872 4061884    0    0     0     0  684 1855  1  3 95  0  0
 0  0      0 8249532 509880 4061876    0    0     0    16  492 1332  0  0 99  0  0
 0  0      0 8249532 509880 4061844    0    0     0     0  379  893  0  0 100  0  0
 0  0      0 8249532 509880 4061844    0    0     0     0  440 1116  0  0 99  0  0

PHP自带的pcntl,存在很多不足,如:

  • pcntl没有提供进程间通信的功能
  • pcntl不支持重定向标准输入和输出
  • pcntl只提供了fork这样原始的接口,容易使用错误
  • swoole_process提供了比pcntl更强大的功能,更易用的API,使PHP在多进程编程方面更加轻松。

Swoole\Process提供了如下特性:

  • 基于Unix Socketsysvmsg消息队列的进程间通信,只需调用write/read或者push/pop即可
  • 支持重定向标准输入和输出,在子进程内echo不会打印屏幕,而是写入管道,读键盘输入可以重定向为管道读取数据
  • 配合Event模块,创建的PHP子进程可以异步的事件驱动模式
  • 提供了exec接口,创建的进程可以执行其他程序,与原PHP父进程之间可以方便的通信

使用实例

  • 子进程异常退出时,自动重启
  • 主进程异常退出时,子进程会继续执行,完成所有任务后退出
(new class{
    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'));
            $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));
            for ($j = 0; $j < 16000; $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;
            }
        }
    }
});


  • Jetbrains

    请问为什swoole_timer_tick在process里边时间设置不准确呢

  • 俩鱼

    创建一个子进程,通过exec方法去调用一个python脚本。如果python脚本中有raw_input()方法时,主进程无法通过管理获取数据。在centos的cli, fpm模式下都不可以。用proc_open调用此python脚本就可以通过pipe获取到数据。不知是哪里使用不当!

  • 俩鱼

    不好意思,打错字了。是“主进程无法通过管道获取数据”。

  • 刘高云

    这个实例中子进程内将最大子进程数设置多1个时, $this->new_index++语句未生效,一直未初始值0,求大神解释?

  • 刘高云

    这个是多进程程序吧,父进程和子进程有自己的数据空间和代码空间,即便使用匿名函数传递参数,子进程也只是获得父进程的副本,子进程的数据空间的修改对父进程的数据空间并不会造成改变。应该使用swoole_table共享空间来做这个实例吧

  • 刘高云

    官方给的示例,父子进程的数据交换不正确,父子进程各有各的数据空间,改变某个进程中成员变量的值并不能影响其他进程的数据空间,不可以。下面是我使用共享内容修改后的示例 <?php class MainProcess{ public $mpid = 0; public $works = []; public $max_process = 5; public static $swoole_table = NULL;

    public function __construct(){
        try{
            self::$swoole_table = new swoole_table(1024);
            self::$swoole_table->column('index', swoole_table::TYPE_INT);//用于父子进程间数据交换
            self::$swoole_table->create();
            swoole_set_process_name('php-ps:master');
            $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_process; $i++){
            $this->createProcess();
        }
    }
    public function createProcess($index=null){
        $process = new swoole_process(function(swoole_process $worker) use ($index) {
            if(is_null($index)){//如果没有指定了索引,新建的子进程,开启计数
                $index=self::$swoole_table->get('index');
                if($index === false){
                    $index = 0;
                }else{
                    $index++;
                }
            }
    
            self::$swoole_table->set('index',array('index'=>$index));
            swoole_set_process_name("php-ps:{$index}");
            for($j=0;$j<16000;$j++){
                $this->checkMpid($worker);
                echo "msg:{$j}\n";
                sleep(1);
            }
        },false, false);
        $pid = $process->start();
        $index=self::$swoole_table->get('index');
        $index=$index['index'];
        $this->works[$index] = $pid;
        return $pid;
    }
    
    public function checkMpid(&$worker){
        if(!swoole_process::kill($this->mpid,0)){ //import! check whether master process is running  
            $worker->exit();
            file_put_contents('/tmp/runtime.log', "Master process exited, I [{$worker['pid']}] also quit\n", FILE_APPEND);
        }
    }
    
    public function rebootProcess($res){
    
        $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)){//$result = array('code' => 0, 'pid' => 15001, 'signal' => 15);
                $ret = swoole_process::wait();
            }else{
                $this->rebootProcess($ret);
            }
        }
    }
    

    }

    new MainProcess();

  • 何曦

    process打错了,打成了precess

  • 程序员小辉

    @刘高云 代码我运行了,不错,学习了。 只不过createProcess函数中的逻辑索引操作是不是应该改成我这样:? public function createProcess($iii,$index=null){ $process = new swoole_process(function(swoole_process $worker) use ($index,$iii) { if(is_null($index)){//如果没有指定了索引,新建的子进程,开启计数 $index=self::$swoole_table->get('index'); if($index === false){ $index['index'] = 0; }else{ $index['index']++; } }

            self::$swoole_table->set('index',array('index'=>$index['index']));//类似于定义一个全局变量,这里必须用数组(文档要求的)
            swoole_set_process_name("php-ps:{$index['index']}");
    
            for($j=0;$jcheckMpid($worker);
                echo "msg:{$j}\n";
                sleep(1);
                #sleep($iii+1);
            }
        },false, false);
        $pid = $process->start();
        $index=self::$swoole_table->get('index');
        $index=$index['index'];
        $this->works[$index] = $pid;
        return $pid;
    }
    

  • 程序员小辉

    @刘高云

    还有, 子进程监听主进程的地方。 $worker->exit()应该写在日志之后。不然执行不到日志那块

  • 吾爱

    子进程不能通过server发消息??

  • 猫空

    子进程不能使用swoole_http_client啊,只能处理本地数据吗

  • 勇闯天涯

    进程管理 process 功能很强大!

  • 飞鸿影

    ~~~ php

  • 飞鸿影

    swoole_process在最新的1.8.0版本已经禁止在Web环境中使用了,所以也只能支持命令行。其实multi-curl是不错的选择,你可以在网上找一下封装好的类。https://group.swoole.com/question/106198

  • 小潘

    demo代码写的有点问题,主进程和子进程要使用共享内存swoole_table来交换数据。

  • sailsweep

    运行之后可以通过 ps -aux | grep php 查看到相关的进程

  • show

    if(is_null($index)){ $index=$this->new_index; $this->new_index++; } 这个应该放在$process = new swoole_process(function(swoole_process $worker) use ($index) {上面,不然在子进程没意义的

  • NickBai

    难道就没有人觉得这个demo 有问题吗?

    if(is_null($index)){
                    $index=$this->new_index;
                    $this->new_index++;
                }
    

    这一段写的是个啥,$this->new_index,在子进程中创建,你再怎么++,别的进程中,拿到的总是 fork之前的值。进程之间变量不共享。也就是 $this->new_index 的值,永远都是 0。导致保存 worker的map 【$this->works】,永远只有一个子进程。当 其中某一个进程,循环完,16000次之后,reboot拉起,就报错。因为map中,压根没有保存这些数据。 我觉得是肯定是因为 16000次的循环还有sleep 1秒,让大多数的新手根本没发现错误所在。

  • NickBai

    修正后的demo,fork 4个进程

    class TaskWorker
    {
        public $mpid = 0;
        public $works = [];
        public $max_precess = 4;
        public $new_index = 0;
    
        public function __construct()
        {
            try {
    
                swoole_set_process_name(sprintf('php-ps:%s', 'master'));
                $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->new_index = $i;
                $this->CreateProcess();
            }
        }
    
        public function CreateProcess($index = null)
        {
            if(is_null($index)) {
                $index = $this->new_index;
            }
    
            $process = new swoole_process(function(swoole_process $worker) use ($index) {
                swoole_set_process_name(sprintf('php-ps:%s', $index));
    
                for ($j = 0; $j < 5; $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(false !== $index){
                $index = intval($index);
                $new_pid = $this->CreateProcess($index);
                echo "rebootProcess: {$index} = {$new_pid} Done" . PHP_EOL;
                return;
            }
            throw new \Exception('rebootProcess Error: no pid' . PHP_EOL);
        }
    
        public function processWait()
        {
            while(1) {
                if(count($this->works)){
                    $ret = \swoole\process::wait();
                    if ($ret) {
                        $this->rebootProcess($ret);
                    }
                }else{
                    break;
                }
            }
        }
    }
    

  • Soul

    文档真的是 ‘太详细了!!!!!’

  • 混乱的sw

    很多时候我们需要利用api去按需创建子进程,比如通过http协议的接口访问一个action,这个action里利用process->exec();去执行一些监控或者重启服务器等脚本,怎么做呢?大家把协程这个鬼关掉然后在需要的控制器了创建子进程就行,无疑这个方式不好,因为关闭推荐的协程服务,意味着损失其它功能,但是有什么办法呢?

  • 混乱的sw

    这里的swoole的多进程的wait等待子进程退出的函数是异步的吗? 下面的示例用的是wait的阻塞等待子进程退出,而且,wait里sleep(50),但是wait外面的程序,却完全无视这个休眠 时间,秒执行,这里的子进程是确确实实退出了,我终端打印出了。 const SHELL_TEMP_FILE = '/data/www/template_msg_push/runtime/shell_temp.txt';

    public function index()
    {
        $temp_path = self::SHELL_TEMP_FILE;
        $shell_script = isset($_POST['script'])? $_POST['script'] : null;
        if (empty($shell_script)) {
            return json(['code' => 0, '没有提交执行的脚本']);
        }
        if (file_exists($temp_path)) {
            unlink($temp_path);
        }
    
        $process = new \Swoole\Process(function (\Swoole\Process $c_process) use ($shell_script, $temp_path) {
            // 检查公众号的服务器是否还在运行
            $c_process->exec('/bin/sh', ['-c', "$shell_script >$temp_path"]);
        });
        $cid = $process->start();
    
        \Swoole\Process::signal(SIGCHLD, function($sig) use($cid, $temp_path) {
            //为false,非阻塞模式
            while($ret =  \Swoole\Process::wait(false)) {
                if ($ret['pid'] = $cid) {
                    echo 'child process has exit';
                    sleep(50);
                    break;
                }
            }
        });
        echo 'dddd' . PHP_EOL;  // 这里根本不等待上面的wait函数,直接执行了
        $log = FileHelper::line_read($temp_path);  // 这里是一个while死循环的去读子进程写的文件,它也没有等到,完全失 
        //效,按逻辑是有文件则,读取,并终结死循环,而这里则是死循环都失效了,直接读,然而此时子进程都没有创建文件
        // 导致读取失败
        // swoole缺乏一种将异步不可靠的执行能够处理成同步的机制
        var_dump($log);
        return json(['code' =>1, 'msg' => '命令已经执行', 'data' => $log]);
    }
    

  • 混乱的sw

    上面的那个读文件的函数,是一个判断异步子进程写进了文件,再读取的函数,这里完全死循环完全失效,不知道是什么 原因,大家帮忙看看吧! public static function line_read($path) { while(true) { if ($fp = fopen($path, 'r')) { $arr = []; while(!feof($fp)) { $arr[] = fgets($fp); } fclose($fp); return $arr; break; } }

    }
    

  • 混乱的sw

    读取失败的原因找到了,是因为子进程还没有创建文件,导致死循环报错终止

  • 混乱的sw

    现在的问题是如何能够让swwole\process::wait()后面的代码在wait执行完成后执行,如果能够实现的话,就可以保证子进程执行完成后,依赖子进程业务的程序就可以在wait后面编写,现在的测试情况是wait后面的代码根本不理会wait

  • 新用户(手机注册)

    请问怎么实现一个主进程,多个子进程,子进程之间通信?

  • 混乱的sw

    进程间的通信用共享内存就行了,swoole的table和redis都是可以的,至于swoole 的进程间的通信包括消息队列,测试了很久,不知道是我测试代码的问题还是啥,能用,但是效率真的低,也特别不稳定。

  • 混乱的sw

    请问process->exec(执行脚本)这个函数执行完后,如何回收子进程,测试了半天,因为子进程的回调里,process->exec()的后面逻辑根本不会运行,所以子进程的回调里无法用process->exit(0)退出子进程,同时回调外面用process->exit(0)居然莫名其妙把主进程整个程序退出了,用process->kill($child_id)报错,然后在回调里想用异步定时器去exit吧,子进程里不支持定时器使用,怎么办嘛? /** * 检查公众号定时服务是否开启 * return json */ public function check() { $log_path = self::SHELL_TEMP_FILE; if (file_exists($log_path)) { // 排除之前写入的干扰 unlink($log_path); } // 开启子进程执行shell命令 $process = new \Swoole\Process(function(\Swoole\Process $c_process)use($log_path) { // 这里又不能用定时器去异步退出子进程 // 检查公众号的服务器是否还在运行 $c_process->exec('/bin/sh', ['-c', "ps -aux|grep PublicTemplateSend.php >$log_path 2>&1"]); // 这个后面的代码不会执行,所以没有办法执行 $c_process->exit(0) }); $cid = $process->start();

        // 检查服务的运行状态
        $res = $this->is_active($log_path);
        // 子进程的回调外面又不能用$process->kill($cid)或者$process->exit(0)(搞笑的是这里居然整个程序都退出)
        $this->wait($cid);
        if ($res['str']) { //服务运行的标识
            return json(['code' => 1, 'msg' => '服务器正在运行', 'data' => $res['data']]);
        } else if ($res['data']) { // 命令执行,且写入临时文件,但是没有服务运行的标识,服务已经终止
            return json(['code' => 1, 'msg' => '服务已经停止,请重启服务', 'data' => $res['data']]);
        } else { // 由于没有成功读取到临时文件,无法判断服务是否正在运行,需要继续检查
            return json(['code' => 0, 'msg' => '服务器繁忙,请重复刚才的操作']);
        }
    }
    

    拿这个process->exec(xxxx)的子进程怎么办,执行一次,一个僵尸进程。

  • 混乱的sw

    public function check() { $log_path = self::SHELL_TEMP_FILE; if (file_exists($log_path)) { // 排除之前写入的干扰 unlink($log_path); } // 开启子进程执行shell命令 $process = new \Swoole\Process(function(\Swoole\Process $c_process)use($log_path) { // 检查公众号的服务器是否还在运行 $c_process->exec('/bin/sh', ['-c', "ps -aux|grep PublicTemplateSend.php >$log_path 2>&1"]); }); $cid = $process->start();

        // 检查服务的运行状态
        $res = $this->is_active($log_path);
        $this->wait($cid);
        if ($res['str']) { //服务运行的标识
            return json(['code' => 1, 'msg' => '服务器正在运行', 'data' => $res['data']]);
        } else if ($res['data']) { // 命令执行,且写入临时文件,但是没有服务运行的标识,服务已经终止
            return json(['code' => 1, 'msg' => '服务已经停止,请重启服务', 'data' => $res['data']]);
        } else { // 由于没有成功读取到临时文件,无法判断服务是否正在运行,需要继续检查
            return json(['code' => 0, 'msg' => '服务器繁忙,请重复刚才的操作']);
        }
    }