什么是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进程间通信,使用简单只需要调用write
、read
、push
、pop
即可。 -
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_errno
和swoole_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;
}
- 子进程会继承父进程的内存和文件句柄
- 子进程在启动时会清除父进程继承的
EventLoop
、Signal
、Timer
。 - 执行后子进程会保持父进程的内存和资源,如果父进程内创建了一个Redis连接,那么在子进程中会保留此对象,所有操作都是对同一个连接进行的。
读取数据read
从管道中读取数据,若管道类型$pipe_type = 1
为SOCK_STREAM
流式时,读取的是流式的,此时需要自行处理包完整性问题。若管道类型$pipe_type = 2
为SOCK_DGRAM
数据报时,可以读取完整的一个数据包。若读取成功则会返回二进制数据字符串,若读取失败则会返回false
。
原型
- 同步阻塞读取
function Process->read(int $buffer_size = 8192): string | bool
- 异步非阻塞读取
若采用异步模式读取,则需使用swoole_event_add
将管道加入到事件循环中,即可变为异步模式。由于Swoole底层采用epoll
的LT
模式,因此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
未完待续...