Process
Swoole提供的进程管理模块,用来替代PHP的pcntl
!> 此模块比较底层,是操作系统进程管理的封装,使用者最好有linux多进程编程经验。
PHP
自带的pcntl
,存在很多不足,如:
- 没有提供进程间通信的功能
- 不支持重定向标准输入和输出
- 只提供了fork这样原始的接口,容易使用错误
Process
提供了比pcntl
更强大的功能,更易用的API
,使PHP在多进程编程方面更加轻松。
Process
提供了如下特性:
- 可以方便的实现进程间通讯
- 支持重定向标准输入和输出,在子进程内
echo
不会打印屏幕,而是写入管道,读键盘输入可以重定向为管道读取数据 - 提供了exec接口,创建的进程可以执行其他程序,与原
PHP
父进程之间可以方便的通信
使用示例
- 创建3个子进程,主进程用wait回收进程
- 主进程异常退出时,子进程会继续执行,完成所有任务后退出
use Swoole\Process;
for ($n = 1; $n <= 3; $n++) {
$process = new Process(function () use ($n) {
echo 'Child #' . getmypid() . " start and sleep {$n}s" . PHP_EOL;
sleep($n);
echo 'Child #' . getmypid() . ' exit' . PHP_EOL;
});
$process->start();
}
for ($n = 3; $n--;) {
$status = Process::wait(true);
echo "Recycled #{$status['pid']}, code={$status['code']}, signal={$status['signal']}" . PHP_EOL;
}
echo 'Parent #' . getmypid() . ' exit' . PHP_EOL;
属性
pid
子进程的PID
。
Swoole\Process->pid: int;
pipe
unixSocket的文件描述符。
Swoole\Process->pipe;
方法
__construct()
构造方法。
Swoole\Process->__construct(callable $function, bool $redirect_stdin_stdout = false, int $pipe_type = SOCK_DGRAM, bool $enable_coroutine = false);
参数
callable $function
- 功能:子进程创建成功后要执行的函数【底层会自动将函数保存到对象的
callback
属性上。如果希望更改执行的函数,可赋值新的函数到对象的callback
属性】 - 默认值:无
- 其它值:无
- 功能:子进程创建成功后要执行的函数【底层会自动将函数保存到对象的
bool $redirect_stdin_stdout
- 功能:重定向子进程的标准输入和输出。【启用此选项后,在子进程内输出内容将不是打印屏幕,而是写入到主进程管道。读取键盘输入将变为从管道中读取数据。默认为阻塞读取。参考
exec()
方法小节内容】 - 默认值:无
- 其它值:无
- 功能:重定向子进程的标准输入和输出。【启用此选项后,在子进程内输出内容将不是打印屏幕,而是写入到主进程管道。读取键盘输入将变为从管道中读取数据。默认为阻塞读取。参考
int $pipe_type
- 功能:unixSocket类型【启用
$redirect_stdin_stdout
后,此选项将忽略用户参数,强制为1
。如果子进程内没有进程间通信,可以设置为0
】 - 默认值:
1
- 其它值:
0
、2
- 功能:unixSocket类型【启用
bool $enable_coroutine
- 功能:在
callback function
中启用协程,开启后可以直接在子进程的函数中使用协程API - 默认值:
false
- 其它值:
true
- 版本影响:Swoole >= v4.3.0
- 功能:在
unixSocket类型 | 说明 |
---|---|
0 | 不创建 |
1 | 创建SOCK_STREAM类型的unixSocket |
2 | 创建SOCK_DGRAM类型的unixSocket |
start()
执行fork
系统调用,启动进程。
Swoole\Process->start(): int|false;
返回值
- 成功返回子进程的
PID
- 失败返回
false
。可使用swoole_errno和swoole_strerror得到错误码和错误信息。
- 成功返回子进程的
注意
!> 执行后子进程会保持父进程的内存和资源,如父进程内创建了一个redis连接,那么在子进程会保留此对象,所有操作都是对同一个连接进行的。以下举例说明
$redis = new Redis;
$redis->connect('127.0.0.1', 6379);
function callback_function() {
swoole_timer_after(1000, function () {
echo "hello world";
});
global $redis;//同一个连接
};
swoole_timer_tick(1000, function () {
echo "parent timer\n";
});//不会继承
Swoole\Process::signal(SIGCHLD, function ($sig) {
while ($ret = Swoole\Process::wait(false)) {
// create a new child process
$p = new Swoole\Process('callback_function');
$p->start();
}
});
// create a new child process
$p = new Swoole\Process('callback_function');
$p->start();
!> 1. 子进程启动后会自动清除父进程中Swoole\Timer::tick创建的定时器、Process::signal监听的信号和swoole_event_add添加的事件监听;
2. 子进程会继承父进程创建的$redis
连接对象,父子进程使用的连接是同一个。
exportSocket()
将unixSocket
导出为Coroutine\Socket
对象,然后利用Coroutine\socket
对象的方法进程间通讯,具体用法请参考Coroutine\socket和IPC通讯。
Swoole\Process->exportSocket(): Swoole\Coroutine\Socket|false;
!> 多次调用此方法,返回的对象是同一个;exportSocket()
导出的socket
是一个新的fd
,当关闭导出的socket
时不会影响进程原有的管道。
由于是Coroutine\Socket
对象,必须在协程容器中使用,所以Swoole\process构造函数$enable_coroutine
参数必须为true。
同样的父进程想用Coroutine\Socket
对象,需要手动Co\run()
以创建协程容器。
返回值
- 成功返回
Coroutine\Socket
对象 - 进程未创建unixSocket,操作失败,返回
false
- 成功返回
使用示例
实现了一个简单的父子进程通讯:
$proc1 = new Swoole\Process(function (swoole_process $proc) {
$socket = $proc->exportSocket();
echo $socket->recv();
$socket->send("hello master\n");
echo "proc1 stop\n";
}, false, 1, true);
$proc1->start();
//父进程创建一个协程容器
Co\run(function() use ($proc1) {
$socket = $proc1->exportSocket();
$socket->send("hello pro1\n");
var_dump($socket->recv());
});
Swoole\Process::wait(true);
比较复杂的通讯例子:
use Swoole\Process;
use Swoole\Timer;
$process = new Process(function ($proc) {
Swoole\Timer::tick(1000, function () use ($proc) {
$socket = $proc->exportSocket();
$socket->send("hello master\n");
echo "child timer\n";
});
}, false, 1, true);
$process->start();
Co\run(function() use ($process) {
Process::signal(SIGCHLD, static function ($sig) {
while ($ret = Swoole\Process::wait(false)) {
/* clean up then event loop will exit */
Process::signal(SIGCHLD, null);
Timer::clearAll();
}
});
/* your can run your other async or coroutine code here */
Timer::tick(500, function () {
echo "parent timer\n";
});
$socket = $process->exportSocket();
while (1) {
var_dump($socket->recv());
}
});
!> 注意默认类型是SOCK_STREAM
,需要处理粘包问题,参考Coroutine\socket的setProtocol()
方法。
使用SOCK_DGRAM
类型进行IPC通讯,可以避免处理粘包问题,参考IPC通讯:
//IPC通讯即使是 SOCK_DGRAM 类型的socket也不需要用 sendto / recvfrom 这组函数,send/recv即可。
$proc1 = new Swoole\Process(function (swoole_process $proc) {
$socket = $proc->exportSocket();
while (1) {
var_dump($socket->send("hello master\n"));
}
echo "proc1 stop\n";
}, false, 2, 1);//构造函数pipe type传为2 即SOCK_DGRAM
$proc1->start();
Co\run(function() use ($proc1) {
$socket = $proc1->exportSocket();
Co::sleep(5);
var_dump(strlen($socket->recv()));//一次recv只会收到一个"hello master\n"字符串 不会出现多个"hello master\n"字符串
});
Swoole\Process::wait(true);
name()
修改进程名称。此函数是swoole_set_process_name的别名。
Swoole\Process->name(string $name): bool;
!> 在执行exec
后,进程名称会被新的程序重新设置;name
方法应当在start
之后的子进程回调函数中使用。
exec()
执行一个外部程序,此函数是exec
系统调用的封装。
Swoole\Process->exec(string $execfile, array $args);
参数
string $execfile
- 功能:指定可执行文件的绝对路径,如
"/usr/bin/python"
- 默认值:无
- 其它值:无
- 功能:指定可执行文件的绝对路径,如
array $args
- 功能:
exec
的参数列表【如array('test.py', 123)
,相当于python test.py 123
】 - 默认值:无
- 其它值:无
- 功能:
执行成功后,当前进程的代码段将会被新程序替换。子进程蜕变成另外一套程序。父进程与当前进程仍然是父子进程关系。
父进程与新进程之间可以通过标准输入输出进行通信,必须启用标准输入输出重定向。
!> $execfile
必须使用绝对路径,否则会报文件不存在错误;
由于exec
系统调用会使用指定的程序覆盖当前程序,子进程需要读写标准输出与父进程进行通信;
如果未指定redirect_stdin_stdout = true
,执行exec
后子进程与父进程无法通信。
- 使用示例
例 1:可以在 Swoole\Process
创建的子进程中使用 Swoole\Server,但为了安全必须在$process->start
创建进程后,调用 $worker->exec()
执行。代码如下:
$process = new Swoole\Process('callback_function', true);
$pid = $process->start();
function callback_function(Swoole\Process $worker)
{
$worker->exec('/usr/local/bin/php', array(__DIR__.'/swoole_server.php'));
}
Swoole\Process::wait();
例 2:启动Yii程序
$process = new \Swoole\Process(function (\Swoole\Process $childProcess) {
// 不支持这种写法
// $childProcess->exec('/usr/local/bin/php /var/www/project/yii-best-practice/cli/yiit/index -m=123 abc xyz');
// 封装 exec 系统调用
// 绝对路径
// 参数必须分开放到数组中
$childProcess->exec('/usr/local/bin/php', ['/var/www/project/yii-best-practice/cli/yii', 't/index', '-m=123', 'abc', 'xyz']); // exec 系统调用
});
$process->start(); // 启动子进程
例3:父进程与exec
子进程使用标准输入输出进行通信:
// exec - 与exec进程进行管道通信
use Swoole\Process;
$process = new Process(function (Process $worker) {
$worker->exec('/bin/echo', ['hello']);
}, true, 1, true); // 需要启用标准输入输出重定向
$process->start();
Co\run(function() use($process) {
$socket = $process->exportSocket();
echo "from exec: " . $socket->recv() . "\n";
});
例4:执行 shell 命令
exec
方法与PHP
提供的shell_exec
不同,它是更底层的系统调用封装。如果需要执行一条shell
命令,请使用以下方法:
$worker->exec('/bin/sh', array('-c', "cp -rf /data/test/* /tmp/test/"));
close()
用于关闭创建的好的unixSocket。
Swoole\Process->close(int $which): bool;
参数
int $which
- 功能:由于unixSocket是全双工的,指定关闭哪一端【默认为
0
表示同时关闭读和写,1
:关闭写,2
关闭读】 - 默认值:
0
- 其它值:无
- 功能:由于unixSocket是全双工的,指定关闭哪一端【默认为
!> 有一些特殊的情况Process
对象无法释放,如果持续创建进程会导致连接泄漏。调用此函数就可以直接关闭unixSocket
,释放资源。
exit()
退出子进程。
Swoole\Process->exit(int $status = 0);
参数
int $status
- 功能:退出进程的状态码【如果为
0
表示正常结束,会继续执行清理工作】 - 默认值:
0
- 其它值:无
!> 清理工作 包括:
PHP
的shutdown_function
- 对象析构(
__destruct
) - 其他扩展的
RSHUTDOWN
函数
- 功能:退出进程的状态码【如果为
如果$status
不为0
,表示异常退出,会立即终止进程,不再执行相关进程终止的清理工作。
在父进程中,执行Process::wait
可以得到子进程退出的事件和状态码。
kill()
向指定pid
进程发送信号。
Swoole\Process::kill(int $pid, int $signo = SIGTERM): bool;
参数
int $pid
- 功能:进程
pid
- 默认值:无
- 其它值:无
- 功能:进程
int $pid
- 功能:发送的信号【
$signo=0
,可以检测进程是否存在,不会发送信号】 - 默认值:
SIGTERM
- 其它值:无
- 功能:发送的信号【
signal()
设置异步信号监听。
Swoole\Process::signal(int $signo, callable $callback): bool;
此方法基于signalfd
和EventLoop是异步IO
,不能用于阻塞的程序中,会导致注册的监听回调函数得不到调度;
同步阻塞的程序可以使用pcntl
扩展提供的pcntl_signal
;
如果已设置了此信号的回调函数,重新设置时会覆盖历史设置。
参数
int $signo
- 功能:信号
- 默认值:无
- 其它值:无
callable $callback
- 功能:回调函数【
$callback
如果为null
,表示移除信号监听】 - 默认值:无
- 其它值:无
- 功能:回调函数【
!> 在Swoole\Server中不能设置某些信号监听,如SIGTERM
和SIGALAM
- 使用示例
Swoole\Process::signal(SIGTERM, function($signo) {
echo "shutdown.";
});
!> 在v4.4.0
版本中如果进程的EventLoop中只有信号监听的事件,没有其他事件(例如Timer定时器等),进程会直接退出。
Swoole\Process::signal(SIGTERM, function($signo) {
echo "shutdown.";
});
Swoole\Event::wait();//这里即进行所谓的`EventLoop`
上述程序不会进入EventLoop,Swoole\Event::wait()
将立即返回,并退出进程。
wait()
回收结束运行的子进程。
Swoole\Process::wait(bool $blocking = true): array|false;
参数
bool $blocking
- 功能:指定是否阻塞等待【默认为阻塞】
- 默认值:
true
- 其它值:
false
返回值
- 操作成功会返回一个数组包含子进程的
PID
、退出状态码、被哪种信号KILL
- 失败返回
false
- 操作成功会返回一个数组包含子进程的
!> 每个子进程结束后,父进程必须都要执行一次wait()
进行回收,否则子进程会变成僵尸进程,会浪费操作系统的进程资源。
如果父进程有其他任务要做,没法阻塞wait
在那里,父进程必须注册信号SIGCHLD
对退出的进程执行wait
。
SIGCHILD信号发生时可能同时有多个子进程退出;必须将wait()
设置为非阻塞,循环执行wait
直到返回false
。
- 示例
Swoole\Process::signal(SIGCHLD, function ($sig) {
//必须为false,非阻塞模式
while ($ret = Swoole\Process::wait(false)) {
echo "PID={$ret['pid']}\n";
}
});
daemon()
使当前进程蜕变为一个守护进程。
Swoole\Process::daemon(bool $nochdir = true, bool $noclose = true): bool;
参数
bool $nochdir
- 功能:是否切换当前目录到根目录【为
true
表示不要切换当前目录到根目录】 - 默认值:
true
- 其它值:
false
- 功能:是否切换当前目录到根目录【为
bool $noclose
- 功能:是否要关闭标准输入输出文件描述符【为
true
表示不要关闭标准输入输出文件描述符】 - 默认值:
true
- 其它值:
false
- 功能:是否要关闭标准输入输出文件描述符【为
!> 蜕变为守护进程时,该进程的PID
将发生变化,可以使用getmypid()
来获取当前的PID
alarm()
高精度定时器,是操作系统setitimer
系统调用的封装,可以设置微秒级别的定时器。定时器会触发信号,需要与Process::signal或pcntl_signal
配合使用。
!> alarm
不能和 Timer 同时使用
Swoole\Process::alarm(int $time, int $type = ITIMER_REAL): bool;
参数
int $time
- 功能:定时器间隔时间【如果为负数表示清除定时器】
- 值单位:微秒
- 默认值:无
- 其它值:无
int $type
- 功能:定时器类型
- 默认值:
ITIMER_REAL
- 其它值:无
定时器类型 | 说明 |
---|---|
0 | 表示为真实时间,触发SIGALAM 信号 |
1 | 表示用户态CPU时间,触发SIGVTALAM 信号 |
2 | 表示用户态+内核态时间,触发SIGPROF 信号 |
返回值
- 设置成功返回
true
- 失败返回
false
,可以使用swoole_errno
得到错误码
- 设置成功返回
使用示例
Swoole\Process::signal(SIGALRM, function () {
static $i = 0;
echo "#{$i}\talarm\n";
$i++;
if ($i > 20) {
Swoole\Process::alarm(-1);
}
});
//100ms
Swoole\Process::alarm(100 * 1000);
setAffinity()
设置CPU
亲和性,可以将进程绑定到特定的CPU
核上。
此函数的作用是让进程只在某几个CPU
核上运行,让出某些CPU
资源执行更重要的程序。
Swoole\Process::setAffinity(array $cpus): bool;
参数
array $cpus
- 功能:绑定
CPU
核 【如array(0,2,3)
表示绑定CPU0/CPU2/CPU3
】 - 默认值:无
- 其它值:无
- 功能:绑定
!> -$cpus
内的元素不能超过CPU
核数;
-CPU-ID
不得超过(CPU
核数 - 1
);
-使用 swoole_cpu_num() 可以得到当前服务器的CPU
核数。