Swoole中的Process模块比原生php提供的pcntl模块,提供了更易用的多进程编程接口。 简单总结,Process模块有如下特点:

· 可以方便的实现进程间通讯
· 支持重定向标准输入和输出,在子进程内 echo 不会打印屏幕,而是写入管道,读键盘输入可以重定向为管道读取数据
· 提供了 exec 接口,创建的进程可以执行其他程序,与原 PHP 父进程之间可以方便的通信
· 在协程环境中无法使用 Process 模块,可以使用 runtime hook+proc_open 实现,参考协程进程管理

让我们先看一下例子:创建 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;

关于上面使用的Process成员属性或函数看不懂?不用着急,现在我们就一一进行讨论。

属性

1) pid:子进程的pid

Swoole\Process->pid: int

2) pipe:unixSocket 的文件描述符。

Swoole\Process->pipe;

方法

1) __contruct():构造方法

Swoole\Process::__construct(callable $function, bool $redirect_stdin_stdout = false, int $pipe_type = SOCK_DGRAM, bool $enable_coroutine = false);

$function:子进程创建成功后要执行的函数【底层会自动将函数保存到对象的 callback 属性上。如果希望更改执行的函数,可赋值新的函数到对象的 callback 属性】

$redirect_stdin_stdout:重定向子进程的标准输入和输出。【启用此选项后,在子进程内输出内容将不是打印屏幕,而是写入到主进程管道。读取键盘输入将变为从管道中读取数据。默认为阻塞读取。】

$pipe_type:unixSocket 类型【启用 $redirect_stdin_stdout 后,此选项将忽略用户参数,强制为 1。如果子进程内没有进程间通信,可以设置为 0】,可选值如下:

$enable_coroutine:在 callback function 中启用协程,开启后可以直接在子进程的函数中使用协程 API。

2) set():设置参数

Swoole\Process->set(array $settings)

可以使用 enable_coroutine 来控制是否启用协程,和构造函数的第四个参数作用一致。

Swoole\Process->set(['enable_coroutine' => true]);

3) start():执行 fork 系统调用,启动子进程。在 Linux 系统下创建一个进程需要数百微秒时间。

Swoole\Process->start(): int|false

返回值:成功返回子进程的 PID。失败返回 false。可使用 swoole_errno 和 swoole_strerror 得到错误码和错误信息。

注意:

子进程会继承父进程的内存和文件句柄

子进程在启动时会清除从父进程继承的 EventLoop、Signal、Timer

4) exportSocket():将 unixSocket 导出为 Coroutine\Socket 对象,然后利用 Coroutine\socket 对象的方法进程间通讯。

Swoole\Process->exportSocket(): Swoole\Coroutine\Socket|false

返回值:成功返回 Coroutine\Socket 对象。进程未创建 unixSocket,操作失败,返回 false

注意:

多次调用此方法,返回的对象是同一个;

exportSocket() 导出的 socket 是一个新的 fd,当关闭导出的 socket 时不会影响进程原有的管道。

由于是 Coroutine\Socket 对象,必须在协程容器中使用,所以 Swoole\Process 构造函数 $enable_coroutine 参数必须为 true。

同样的父进程想用 Coroutine\Socket 对象,需要手动 Co\run() 以创建协程容器。

示例:

use Swoole\Process;

$proc1 = new Process(function (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());
});
Process::wait(true);

注意:默认类型是 SOCK_STREAM,需要处理粘包问题,参考 Coroutine\socket 的 setProtocol() 方法。使用 SOCK_DGRAM 类型进行 IPC 通讯,可以避免处理粘包问题。

示例:

use Swoole\Process;

//IPC通讯即使是 SOCK_DGRAM 类型的socket也不需要用 sendto / recvfrom 这组函数,send/recv即可。
$proc1 = new Process(function (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"字符串
}); Process::wait(true);

5) name():修改进程名称。此函数是 swoole_set_process_name 的别名。

Swoole\Process->name(string $name): bool

注意:在执行 exec 后,进程名称会被新的程序重新设置;name 方法应当在 start 之后的子进程回调函数中使用。

6) exec():执行一个外部程序,此函数是 exec 系统调用的封装。

Swoole\Process->exec(string $execfile, array $args);

$execfile:指定可执行文件的绝对路径,如 "/usr/bin/python"。必须使用绝对路径,否则会报文件不存在错误。

$args:exec 的参数列表【如 array('test.py', 123),相当于 python test.py 123】

注意:由于 exec 系统调用会使用指定的程序覆盖当前程序,子进程需要读写标准输出与父进程进行通信;如果未指定 redirect_stdin_stdout = true,执行 exec 后子进程与父进程无法通信。

示例1:

$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:父进程与子进程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";
});

示例3:执行shell

$worker->exec('/bin/sh', array('-c', "cp -rf /data/test/* /tmp/test/"));

7) close():用于关闭创建的好的 unixSocket。

Swoole\Process->close(int $which): bool

$which:由于 unixSocket 是全双工的,指定关闭哪一端【默认为 0 表示同时关闭读和写,1:关闭写,2 关闭读】

注意:有一些特殊的情况 Process 对象无法释放,如果持续创建进程会导致连接泄漏。调用此函数就可以直接关闭 unixSocket,释放资源。

8) exit():退出子进程。

Swoole\Process->exit(int $status = 0);

$status:退出进程的状态码【如果为 0 表示正常结束,会继续执行清理工作】

注意事项:

清理工作包括:PHP 的 shutdown_function、对象析构(__destruct)、其他扩展的 RSHUTDOWN 函数

如果 $status 不为 0,表示异常退出,会立即终止进程,不再执行相关进程终止的清理工作。

在父进程中,执行 Process::wait 可以得到子进程退出的事件和状态码。

9) kill():向指定 pid 进程发送信号。

Swoole\Process::kill(int $pid, int $signo = SIGTERM): bool

$pid:进程pid

$signo:发送的信号【$signo=0,可以检测进程是否存在,不会发送信号】

10) signal():设置异步信号监听。

Swoole\Process::signal(int $signo, callable $callback): bool

$signo:信号

$callback:回调函数【$callback 如果为 null,表示移除信号监听】

注意:

此方法基于 signalfd 和 EventLoop 是异步 IO,不能用于阻塞的程序中,会导致注册的监听回调函数得不到调度;

同步阻塞的程序可以使用 pcntl 扩展提供的 pcntl_signal;

如果已设置了此信号的回调函数,重新设置时会覆盖历史设置。

在 Swoole\Server 中不能设置某些信号监听,如 SIGTERM 和 SIGALAM。

如果进程的 EventLoop 中只有信号监听的事件,没有其他事件 (例如 Timer 定时器等),进程会直接退出。

示例1:

Swoole\Process::signal(SIGTERM, function($signo) {
echo "shutdown.";
});

示例2:

//以下程序不会进入 EventLoop,Swoole\Event::wait() 将立即返回,并退出进程。
Swoole\Process::signal(SIGTERM, function($signo) {
echo "shutdown.";
});
Swoole\Event::wait();

11) wait():回收结束运行的子进程。

Swoole\Process::wait(bool $blocking = true): array|false

$blocking:指定是否阻塞等待【默认为阻塞】

返回值:操作成功会返回一个数组包含子进程的 PID、退出状态码、被哪种信号 KILL;失败返回 false

注:推荐使用协程版本的 Swoole\Coroutine\System::wait()

注意事项:

每个子进程结束后,父进程必须都要执行一次 wait() 进行回收,否则子进程会变成僵尸进程,会浪费操作系统的进程资源。

如果父进程有其他任务要做,没法阻塞 wait 在那里,父进程必须注册信号 SIGCHLD 对退出的进程执行 wait。

SIGCHILD 信号发生时可能同时有多个子进程退出;必须将 wait() 设置为非阻塞,循环执行 wait 直到返回 false。

示例:

<?php
use Swoole\Process; for($i=1;$i<=3;$i++)
{
$process=new Process(function() use ($i){
Swoole\Timer::tick(1000, function () {
echo "child timer:",getmypid(),PHP_EOL;
});
},false,1,true);
$process->start();
} Process::signal(SIGCHLD, static function ($sig) {
while ($ret = Process::wait(false)) {
echo "PID={$ret['pid']}\n";
}
}); //这里很重要,要注意:Timer::tick必须存在(即后面的操作必须是异步操作,否能是同步操作)
Swoole\Timer::tick(2000, function () {
echo "parent timer\n";
});

12) daemon():使当前进程蜕变为一个守护进程。

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

$nochdir:是否切换当前目录到根目录【为 true 表示不要切换当前目录到根目录】

$noclose:是否要关闭标准输入输出文件描述符【为 true 表示不要关闭标准输入输出文件描述符】

注意:蜕变为守护进程时,该进程的 PID 将发生变化,可以使用 getmypid() 来获取当前的 PID

13) alarm():高精度定时器,是操作系统 setitimer 系统调用的封装,可以设置微秒级别的定时器。定时器会触发信号,需要与 Process::signal 或 pcntl_signal 配合使用。

Swoole\Process::alarm(int $time, int $type = 0): bool

$time:定时器间隔时间【如果为负数表示清除定时器】,单位:微秒

$type:定时器类型

返回值:设置成功返回 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);

注意:alarm 不能和 Timer 同时使用。

14) setAffinity():设置 CPU 亲和性,可以将进程绑定到特定的 CPU 核上。此函数的作用是让进程只在某几个 CPU 核上运行,让出某些 CPU 资源执行更重要的程序。

Swoole\Process::setAffinity(array $cpus): bool

$cpus:绑定 CPU 核 【如 array(0,2,3) 表示绑定 CPU0/CPU2/CPU3】

注意:

- $cpus 内的元素不能超过 CPU 核数;

- CPU-ID 不得超过(CPU 核数 - 1);

- 使用 swoole_cpu_num() 可以得到当前服务器的 CPU 核数。

15) setPriority():设置进程、进程组和用户进程的优先级。

Swoole\Process->setPriority(int $which, int $priority): bool

$witch:决定修改优先级的类型

$priority:优先级。值越小,优先级越高。可选值范围 [-20,20]

返回值:如果返回 false,可使用 swoole_errno 和 swoole_strerror 得到错误码和错误信息。

16) getPriority():获取进程的优先级。

Swoole\Process->getPriority(int $which): int

进程类的介绍到这里就结束了,下一节我们将进入进程池话题:)

---------------------------  我是可爱的分割线  ----------------------------

最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。

Swoole从入门到入土(22)——多进程[Process]的更多相关文章

  1. RocketMQ入门到入土(二)事务消息&顺序消息

    接上一篇:RocketMQ入门到入土(一)新手也能看懂的原理和实战! 一.事务消息的由来 1.案例 引用官方的购物案例: 小明购买一个100元的东西,账户扣款100元的同时需要保证在下游的积分系统给小 ...

  2. Hexo结合Stun静态博客搭建从入门到入土

    摘要 安装npm,安装hexo相关依赖,安装主题stun 修改hexo配置,修改stun配置,部署到github,gitee实现静态访问 给博客加上全局搜索,访问量统计 hexo博客编写模板 tips ...

  3. QT从入门到入土(一)——Qt5.14.2安装教程和VS2019环境配置

    引言 24岁的某天,承载着周围人的关心,一路南下.天晴心静,听着斑马,不免对未来有些彷徨.但是呢,人生总要走陌生的路,看陌生的风景,所幸可以听着不变的歌,关心自己的人就那么多.就像是对庸常生活的一次越 ...

  4. QT从入门到入土(二)——对象模型(对象树)和窗口坐标体系

    摘要 我们使用的标准 C++,其设计的对象模型虽然已经提供了非常高效的 RTTI 支持,但是在某些方面还是不够灵活.比如在 GUI 编程方面,既需要高效的运行效率也需要强大的灵活性,诸如删除某窗口时可 ...

  5. QT从入门到入土(三)——信号和槽机制

    摘要 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号 (signal).这种发出是没有目的的,类似广播 ...

  6. QT从入门到入土(四)——多线程(QtConcurrent::run())

    引言 在前面对Qt多线程(QThread)做了详细的分析:QT从入门到入土(四)--多线程(QThread) - 唯有自己强大 - 博客园 (cnblogs.com) 但是最近在做项目时候,要将一个函 ...

  7. 多进程Process

    多进程旧式写法 from multiprocessing import Pool def f(x): return x*x if __name__ == '__main__': p = Pool(5) ...

  8. Python中if __name__=="__main__" 语句在调用多进程Process过程中的作用分析

    2018年2月27日 于创B515 引言 最近准备学习一下如何使用Python中的多进程.在翻看相关书籍.网上资料时发现所有代码都含有if __name__=="__main__" ...

  9. 创建多进程Process

    注册一个进程: from multiprocessing import Process import os def func(args): # 在子进程里面.args接收一个参数,如果要接受多个参数使 ...

  10. 从入门到入土:Lambda完整学习指南,包教包会!

    什么是Lambda表达式 Lambda表达式是Java SE 8中一个重要的新特性.lambda表达式允许你通过表达式来代替功能接口.lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使 ...

随机推荐

  1. [STM32H7] 实战技能分享,如何让工程代码各种优化等级通吃,含MDK AC5,AC6,IAR和GCC

    引出问题:    一个好的工程项目代码,特别是开源类的,如果能做到各种优化等级通吃,是一种非常好的工程案例,这样别人借鉴的时候,可以方便的适配到自己工程里.但实际项目中,针对一款产品代码,我们一般不会 ...

  2. 青少年CTF训练平台 — CRYPTO通关WP

    A2-Crypto Caesar vxshyk{g9g9g099-hg33-4f60-90gk-ikff1f36953j} 凯撒解码 qsnctf{b9b9b099-cb33-4a60-90bf-df ...

  3. [转帖]设置CMD默认代码页为65001或936

    https://www.cnblogs.com/songzhenhua/p/9312769.html 之前不知道怎么改的,CMD的代码页被默认设置成了65001   但我右击CMD标题,选择'默认值' ...

  4. [转帖]netperf - 网络测试工具

    1. 概述 Netperf是一种网络性能的测量工具,主要针对基于TCP或UDP的传输.Netperf根据应用的不同,可以进行不同模式的网络性能测试,即批量数据传输(bulk data transfer ...

  5. [转帖]银河麒麟高级服务器操作系统V10SP1安装Docker管理工具(Portainer+DockerUI)

    文章目录 一.系统环境配置 二.安装Docker 三.安装Docker管理工具 Docker管理工具之Portainer Portainer简介 Portainer安装 Portainer访问测试 D ...

  6. mysql8 initialize 命令 初学版 lower_case_table_names

    1. 今天开发找我跟我说 我安装的mysql 不对. 比较蛋疼.  需要修改一个参数 但是数据库已经初始进去了  重装起来比较麻烦. 硬着头皮搞. 2. 参数的名字为: lower_case_tabl ...

  7. adb驱动安装

    学会adb,工资涨一千 win系统安装 1.安装adb首先需要去官网下载adb安装包,下载完成后解压会有一个adb目录以及目录下四个文件 2.然后将adb目录mv到C:\Windows下,配置环境变量 ...

  8. 2022 倒带 - NutUI

    作者:京东零售 于明明 前言 时光飞逝,流年似水,让我们倒带 2022,回首这跌宕起伏一年走过的 "升级之路". NutUI 表现如何? 成绩单等着您打分! 2022 是 NutU ...

  9. 可插拔组件设计机制—SPI

    作者:京东物流 孔祥东 1.SPI 是什么? SPI 的全称是Service Provider Interface,即提供服务接口:是一种服务发现机制,SPI 的本质是将接口实现类的全限定名配置在文件 ...

  10. echarts显示地图

    <template> <div class="managingPatientSize"> <div id="china-map"& ...