Swoole Process

swoole_process

什么是swoole_process呢?

  • swoole_process是基于C语言封装的进程管理模块,方便PHP多进程编程。
  • swoole_process内置管道、消息队列接口,可以方便地实现进程间通信。
  • swoole_process提供了自定义信号管理

swoole_process是swoole提供的进程管理模块,用来替代PHP的pcntl扩展。

PHP自带的pcntl扩展有什么缺陷呢?

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

使用vmstat指令查看操作系统美妙进程切换的次数。

$ vmstat 1 1000

swoole_process有什么特性呢?

进程在系统中是非常昂贵的资源,创建进程开销很大,另外创建的进程过多会导致进程切换开销大幅上升。

  • swoole_process提供了基于UNIX SOCK进程间通信,使用简单只需要调用writereadpushpop即可。
  • swoole_process支持重定向标准输入和输出,在子进程中echo不会打印到屏幕而会写入到管道,读取键盘输入也可以重定向为管道读取数据。
  • 配置swoole_event模块,创建PHP子进程可以异步的事件驱动模式。
  • swoole_process提供了exec接口,创建进程可以执行其他程序,与原PHP父进程之间可以方便地通信。

例如:创建管道类型的子进程,定时向子进程的管道中写入数据并读取。

$ vim process.php
<?php
class Process
{
    private $process;
    /**构造函数 */
    public function __construct($redirect_stdin_stdout = false, $pipe_type = 1, $deamon = false)
    {
        //创建子进程
        $this->process = new swoole_process([$this, "run"], $redirect_stdin_stdout, $pipe_type);
        //是否设置为后台守护进程
        if($deamon){
            $nochdir = true;//是否切换当前目录到根目录
            $noclose = false;//是否关闭标准输入输出的文件描述符
            $this->process->daemon($nochdir, $noclose);
        }
        //启动子进程,成功返回子进程的PID。
        $pid = $this->process->start();
        if(!$pid){
            $errno = swoole_errno();//获取最近一次系统调用的错误码
            $errtype = 1;//错误类型 1表示标准的UNIX Error
            $errmsg = swoole_strerror($errno, $errtype);//将错误码转换为错误信息
            echo "errno {$errno} errmsg {$errmsg}".PHP_EOL;
        }else{
            echo "pid {$pid}".PHP_EOL;
        }
        //{"pipe":null,"callback":null,"msgQueueId":null,"msgQueueKey":null,"pid":5657,"id":null}
        //echo json_encode($this->process);
        //是否使用管道
        if($pipe_type > 0){
        //获取子进程的管道
        $pipe = $this->process->pipe;
        //echo $pipe.PHP_EOL;
        //异步非阻塞读取:将管道添加到底层reactor事件监听中
        swoole_event_add($pipe, function($pipe){
            //子进程从管道中读取数据
            $recv = $this->process->read();
            echo "[read] {$recv}".PHP_EOL;
        });
        }
    }
    /** 运行子进程*/
    public function run($worker)
    {
        //设置间隔时钟定时器
        $msec = 1000;
        swoole_timer_tick($msec, function($timer_id){
            static $index = 0;
            $index = $index + 1;
            //子进程写入消息
            $message = "hello";
            $result = $this->process->write($message);
            if($result === false){
                $errno = swoole_last_error();//获取最近一次Swoole底层的错误码
                $errmsg = swoole_strerror($errno, 9);//将错误码转化为错误信息
                echo "[error] errno {$errno} errmsg:{$errmsg}".PHP_EOL;
            }else{
                echo "[write] success {$result} bytes".PHP_EOL;
            }
            //写入10次
            if($index == 3){
                //删除定时器
                swoole_timer_clear($timer_id);
            }
        });
    }
}

$process = new Process();
//设置异步信号监听
swoole_process::signal(SIGCHLD, function($signo){
    //收回结束运行的子进程,非阻塞模式
    while($ret = swoole_process::wait(false)){
        echo "[wait] ".json_encode($ret).PHP_EOL;
        echo "pid = ".$ret["pid"].PHP_EOL;
    }
});

运行

$ php process.php
pid 6478
[write] success 5 bytes
[read] hello
[write] success 5 bytes
[read] hello
[write] success 5 bytes
[read] hello
[wait] {"pid":6478,"code":0,"signal":0}
pid = 6478

构造函数construct

创建子进程

原型

swoole_process:__construct(
  callable $function,
  $redirect_stdin_stdout = false,
  $create_pipe = true
)

参数

  • 参数1:callable $function 子进程创建成功后要执行的回调函数
  • 参数2:$redirect_stdin_stdout 重定向子进程的标准输入和输出
  • 参数3:$pipe_type 管道类型
  • 参数4:$enable_coroutine 是否启用协程

返回

$this->process = new swoole_process(
  [$this, "run"], 
  $redirect_stdin_stdout, 
  $pipe_type
);
echo json_encode($this->process);

打印输出

{
  "pipe":null,//管道的文件描述符
  "callback":null,
  "msgQueueId":null,
  "msgQueueKey":null,
  "pid":5657,//当前进程的PID
  "id":nul//当前进程的ID
l}

由此可以获得子进程的管道文件描述符

$pipe = $this->process->pipe;

守护进程 daemon

使当前进程蜕变为一个守护进程,蜕变为守护进程时当前进程的PID将会发生变化,可使用getmypid()获取当前进程的PID。

原型

低于Swoole1.9.1版本

bool Process::daemon(
  bool $nochdir = false, 
  bool $noclose = false
);

高于或等于Swoole1.9.1版本,修改了参数默认值。

bool Process::daemon(
  bool $nochdir = true, 
  bool $noclose = true
);

参数

  • 参数1:bool $nochdir 是否切换当前目录为根目录
  • 参数2:bool $noclose 是否关闭标准输入输出文件描述符

启动进程 start

执行fork系统调用启动进程

原型

function Process->start():int

若子进程启动(创建)成功则返回其PID,若创建失败则返回false。可使用swoole_errnoswoole_strerror获得错误码和错误信息。

//启动子进程,成功返回子进程的PID。
$pid = $this->process->start();
if(!$pid){
    $errno = swoole_errno();//获取最近一次系统调用的错误码
    $errtype = 1;//错误类型 1表示标准的UNIX Error
    $errmsg = swoole_strerror($errno, $errtype);//将错误码转换为错误信息
    echo "errno {$errno} errmsg {$errmsg}".PHP_EOL;
}else{
    echo "pid {$pid}".PHP_EOL;
}
  • 子进程会继承父进程的内存和文件句柄
  • 子进程在启动时会清除父进程继承的EventLoopSignalTimer
  • 执行后子进程会保持父进程的内存和资源,如果父进程内创建了一个Redis连接,那么在子进程中会保留此对象,所有操作都是对同一个连接进行的。

读取数据read

从管道中读取数据,若管道类型$pipe_type = 1SOCK_STREAM流式时,读取的是流式的,此时需要自行处理包完整性问题。若管道类型$pipe_type = 2SOCK_DGRAM数据报时,可以读取完整的一个数据包。若读取成功则会返回二进制数据字符串,若读取失败则会返回false

原型

  • 同步阻塞读取
function Process->read(int $buffer_size = 8192): string | bool
  • 异步非阻塞读取

若采用异步模式读取,则需使用swoole_event_add将管道加入到事件循环中,即可变为异步模式。由于Swoole底层采用epollLT模式,因此swoole_event_add添加到事件监听后,在事件发生后回调函数中必须调用read方法读取socket中的数据,否则底层会持续触发事件回调。

//获取子进程的管道
$pipe = $this->process->pipe;
//echo $pipe.PHP_EOL;
//异步非阻塞读取:将管道添加到底层reactor事件监听中
swoole_event_add($pipe, function($pipe){
    //子进程从管道中读取数据
    $data = $this->process->read();
    echo "[recv] {$data}".PHP_EOL;
});

参数

  • int $buffer_size 表示缓冲区的大小,默认为8192字节即8KB,最大不要超过64KB。

写入数据write

向管道内写入数据,Swoole底层使用UNIX Sock实现通信,UNIX Sock是内核实现的全内存通信,无任何IO消耗。管道通信默认是流式的SOCK_STREAM,写入的数据在读取时可能会被底层合并,可以设置swoole_process构造函数的第三个参数$pipe_type管道类型为2即SOCK_DGRAM使其改变为数据报式。

原型

function Process->write(string $data) int | bool;

参数

  • string $data 表示要发送的数据

返回

  • 写入成功:返回写入数据的字节数
  • 写入失败:返回false可使用swoole_last_error()获取错误码。

例如

//子进程写入消息
$message = "hello";
$result = $this->process->write($message);
if($result === false){
    $errno = swoole_last_error();//获取最近一次Swoole底层的错误码
    $errmsg = swoole_strerror($errno, 9);//将错误码转化为错误信息
    echo "[error] errno {$errno} errmsg:{$errmsg}".PHP_EOL;
}else{
    echo "[write] success {$result} bytes".PHP_EOL;
}

回收进程wait

回收结束运行的子进程,子进程结束必须要执行wait进行回收,否则子进程会变成僵尸进程。

使用Process作为监控父进程,创建管理子进程时,父类必须注册信号SIGCHLD对退出的进程执行wait,否则子进程一旦被kill将会引起父进程退出。

//设置异步信号监听
swoole_process::signal(SIGCHLD, function($signo){
    //收回结束运行的子进程,非阻塞模式
    while($ret = swoole_process::wait(false)){
        echo json_encode($ret).PHP_EOL;
        echo "pid = ".$ret["pid"].PHP_EOL;
    }
});

原型

array Process::wait(bool $blocking = true);

参数

$blocking仅仅在Swoole1.7.10+版本中可用。bool $blocking = true 表示可以指定是否阻塞等待,默认为阻塞。

返回

  • 操作成功:返回一个数组包含子进程的PID、退出状态码、哪种信号KILL
{
  "pid":6478,
  "code":0,
  "signal":0
}
  • 操作失败:返回false

未完待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容