Swoole Process

4933701-e6231cbbcc1ab384.png
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

未完待续...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值