百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

swoole基础进阶之进程篇-笔记 swoole的进程模型

liebian365 2024-10-17 14:04 18 浏览 0 评论

温馨提示:有需要学习swoole可以直接留言评论,分享完整资源给大家。

01开张课、照镜子、设置自定义进程名称

获取当前进程ID

https://www.php.net/manual/zh/function.posix-getpid.php

<?php
$id = posix_getpid();
echo $id;
while (1){
    sleep(10);
}

//查看进程 ps -ef | grep xxx

设置进程名称

https://www.php.net/manual/zh/function.cli-set-process-title

<?php
$id = posix_getpid();
cli_set_process_title('001');
echo $id;
while (1){
    sleep(10);
}

02创建第一个子进程、获取子进程ID、回收子进程

  • defunct死进程

root 7986 7985 0 11:18 pts/1 00:00:00 [php] <defunct>

<?php
$id = posix_getpid();
cli_set_process_title('php1');
echo $id.PHP_EOL;

$child =  new \Swoole\Process(function(){
    echo '我是一个子进程'.posix_getpid().PHP_EOL;
});
$child->start();

while (1){
    sleep(10);
}

使用主进程创建子进程,每个子进程结束后子进程没有被主进程收回资源,子进程会变成僵尸进程。

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

<?php
 
//主进程代码
$id = posix_getpid();
cli_set_process_title('php-main');
echo $id.PHP_EOL;

$child =  new \Swoole\Process(function(){
    //子进程执行
    cli_set_process_title('php-child');
    echo '我是一个子进程'.posix_getpid().PHP_EOL;
    while (1){ //死循环,不让进程不退出
        sleep(10);
    }
});
$child->start();
\Swoole\Process::wait(); //回收子进程资源

//主进程代码
while (1){
    sleep(10);
}

03重定向子进程标准输出 、父进程获取子进程数据初步

父子进程相互通信并且交互数据

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

bool $redirect_stdin_stdout

  • 功能:重定向子进程的标准输入和输出。【启用此选项后,在子进程内输出的内容将不是打印屏幕,而是写入到主进程管道。读取键盘输入将变为从管道中读取数据。默认为阻塞读取。参考 exec() 方法内容】
<?php
$id = posix_getpid();
cli_set_process_title('php-main');
echo $id.PHP_EOL;

$child =  new \Swoole\Process(function(){
    //子进程执行
    cli_set_process_title('php-child');
    echo 'my name is:';
    while (1){
        sleep(10);
    }
},true);  // true 重定向写入到主进程管道
$child->start();

//从管道中读取数据 https://wiki.swoole.com/wiki/page/217.html
echo $child->read().'jackin.chen';

\Swoole\Process::wait();

//主进程代码
while (1){
    sleep(10);
}
  • \Swoole\Process::wait() 回收结束运行的子进程。
array Process::wait(bool $blocking = true);
$result = array('code' => 0, 'pid' => 15001, 'signal' => 15);
  • $blocking 参数可以指定是否阻塞等待,默认为阻塞
  • 操作成功会返回一个数组包含子进程的PID、退出状态码、被哪种信号KILL
  • 失败返回false

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

<?php
$id = posix_getpid();
cli_set_process_title('php-main');
echo $id.PHP_EOL;

$child =  new \Swoole\Process(function(){
    //子进程执行
    cli_set_process_title('php-child');
    $num = 0;
    while (1){
        echo 'my name is:'.++$num;
        sleep(2);
    }
},true);  // true 重定向写入到主进程管道
$child->start();
\Swoole\Process::wait(false); // 非阻塞
//主进程代码
while (1){
    echo $child->read().'jackin.chen'.PHP_EOL;
    sleep(2);
}

04多个子进程的回收、Linux信号入门

<?php
cli_set_process_title('php-main');

echo '当前进程ID:'.posix_getpid().PHP_EOL;
use \Swoole\Process;
$child1 =  new Process(function() {
    //子进程执行
    cli_set_process_title('php-child1');
    echo 'child1:'.posix_getpid().PHP_EOL;
    while (1) {
        sleep(2);
    }
});
$child1->start();

$child2 =  new Process(function() {
    cli_set_process_title('php-child2');
    echo 'child2:'.posix_getpid().PHP_EOL;
});
$child2->start();

Process::wait(); //回收子进程
  • 执行脚本:进程直接退出,因为其中有一个子进程wait退出,子进程一旦被kill会引起父进程退出。
  • 解决方案
<?php
cli_set_process_title('php-main');

echo '当前进程ID:'.posix_getpid().PHP_EOL;
use \Swoole\Process;
$child1 =  new Process(function() {
    //子进程执行
    cli_set_process_title('php-child1');
    echo 'child1:'.posix_getpid().PHP_EOL;
    while (1) {
        sleep(2);
    }
});
$child1->start();

$child2 =  new Process(function() {
    cli_set_process_title('php-child2');
    echo 'child2:'.posix_getpid().PHP_EOL;
});
$child2->start();

//第一种方案
/*
for($i=0;$i<2;$i++){
    Process::wait(); //等待回收子进程
}*/

//第二种方案  :在异步信号回调中执行wait
Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        var_dump($ret);
    }
});
//信号发生时可能同时有多个子进程退出
//必须循环执行wait直到返回false

[root@localhost ~]# ps -ef | grep php

root 16569 6315 0 14:15 pts/1 00:00:00 php-main

root 16570 16569 0 14:15 pts/1 00:00:00 php-child1

Linux信号完整对照表

信号

取值

默认动作

含义(发出信号的原因)

SIGHUP

1

Term

终端的挂断或进程死亡

SIGINT

2

Term

来自键盘上的中断信号

SIGQUIT

3

Core

来自键盘上的离开信号

SIGILL

4

Core

非法指令

SIGABRT

6

Core

来自abort的异常信号

SIGFPE

8

Core

浮点例外

SIGKILL

9

Term

杀死

SIGSEGV

11

Core

段非法错误(内存引用无效)

SIGPIPE

13

Term

管道损坏:向一个没有读进程的管道写数据

SIGALRM

14

Term

来自alarm的计时器导师

  • 了解下信号 一个最简单的例子:我们按下键盘ctd±a我们的进程就停止了。 这就是信号 信号是进程间通信机制中唯一的异步通信机制,一个进程不必通 过任何操作来等待信号的到达,事实上,进程也不知道信号到底是什么 么时候到达。进程之间可以互相通过系统调用kill发送软中断信号

05在子进程中运行httpserver、修改进程名称

Server只能用于php-cli环境,在其他环境下会抛出致命错误

构建Http\Server对象

$serv = new Swoole\Server('0.0.0.0', 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP);

创建一个异步服务器程序,支持TCPUDPUnixSocket 3种协议,支持IPv4IPv6,支持SSL/TLS单向双向证书的隧道加密。使用者无需关注底层实现细节,仅需要设置网络事件的回调函数即可。

请勿在使用Server创建之前调用其他异步IOAPI,否则将会创建失败。可以在Server启动后onWorkerStart回调函数中使用。

设置运行时参数

$serv->set(array(
    'worker_num' => 4,
    'daemonize' => true,
    'backlog' => 128,
));

注册事件回调函数

$serv->on('Connect', 'my_onConnect');
$serv->on('Receive', 'my_onReceive');
$serv->on('Close', 'my_onClose');

PHP中可以使用4种回调函数的风格

启动服务器

$serv->start();

属性列表

$serv->manager_pid;  //管理进程的PID,通过向管理进程发送SIGUSR1信号可实现柔性重启
$serv->master_pid;  //主进程的PID,通过向主进程发送SIGTERM信号可安全关闭服务器
$serv->connections; //当前服务器的客户端连接,可使用foreach遍历所有连接

运行流程图

进程/线程结构图

onStart

启动后在主进程(master)的主线程回调此函数,函数原型

function onStart(Server $server);

在此事件之前Server已进行了如下操作

  • 已创建了manager进程
  • 已创建了worker的进程
  • 已监听所有TCP/UDP/UnixSocket端口,但未开始Accept连接和请求
  • 已经监听了定时器

接下来要执行

  • Reactor开始接收事件,客户端可以connectServer

onStart回调中,仅允许echo、打印Log、修改进程名称。不得执行其他操作。onWorkerStart和onStart回调是在不同进程中并行执行的,不存在先后顺序。

可以在onStart回调中,将$serv->master_pid$serv->manager_pid的值保存到一个文件中。这样可以编写脚本,向这两个PID发送信号来实现关闭和重启的操作。

onStart事件在Master进程的主线程中被调用。

onStart中创建的全局资源对象不能在Worker进程中被使用,因为发生onStart调用时,worker进程已经创建好了 新创建的对象在主进程内,Worker进程无法访问到此内存区域 因此全局对象创建的代码需要放置在Server::start之前

<?php
use \Swoole\Process;
echo '当前进程ID:'.posix_getpid().PHP_EOL;

cli_set_process_title('swoole-main');

$child =  new Process(function() {
    $http = new Swoole\Http\Server("0.0.0.0", 9501);
    $http->set([
        "worker_num"=>4
    ]);
    $http->on('request', function ($request, $response) {
        $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
    });
    $http->on('start', function ( $serv) {
        cli_set_process_title('swoole-master');
    });
    $http->on('managerstart', function ($serv) {
        cli_set_process_title('swoole-manager');
    });

    $http->on('workerstart', function ($serv, $worker_id) {
        cli_set_process_title('swoole-worker');
    });

    $http->start();
});

$child->start();
echo '当前进程名称:'.cli_get_process_title().PHP_EOL;
Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        var_dump($ret);
    }
});

事件

Swoole\Server是事件驱动模式,所有的业务逻辑代码必须写在事件回调函数中。当特定的网络事件发生后,底层会主动回调指定的PHP函数。

  • 共支持13种事件,具体详情请参考各个页面详细页
  • PHP语言有4种回调函数的写法

事件执行顺序

  • 所有事件回调均在$server->start后发生
  • 服务器关闭程序终止时最后一次事件是onShutdown
  • 服务器启动成功后,onStart/onManagerStart/onWorkerStart会在不同的进程内并发执行
  • onReceive/onConnect/onCloseWorker进程中触发
  • Worker/Task进程启动/结束时会分别调用一次onWorkerStart/onWorkerStop
  • onTask事件仅在task进程中发生
  • onFinish事件仅在worker进程中发生

onStart/onManagerStart/onWorkerStart 3个事件的执行顺序是不确定的

异常捕获

  • swoole不支持set_exception_handler函数
  • 如果你的PHP代码有抛出异常逻辑,必须在事件回调函数顶层进行try/catch来捕获异常
$serv->on('Receive', function() {
    try
    {
        //some code
    }
    catch(Exception $e)
    {
        //exception code
    }
}

协程模式

Swoole2/4版本支持了协程,使用协程后事件回调函数将会并发地执行。协程是一种用户态线程实现,没有额外的调度消耗,仅占用内存。使用协程模式,可以理解为“每次事件回调函数都会创建一个新的线程去执行,事件回调函数执行完成后,线程退出”。

如果希望关闭协程,可设置:

$server->set(["enable_coroutine" => false, ]);

06场景练习监控文件变动、进程感知

1、比如有个指定配置文件db.conf在当前 项目目录下的tmp文件夹中 2、文件一旦发生变动则进程能感知到,然后发送什么消息等等 3、这种功能是很适合用多进程监控来做的

<?php
use \Swoole\Process;
echo '当前进程ID:'.posix_getpid().PHP_EOL;
cli_set_process_title('swoole-main');
$child =  new Process(function() {
   $file = __DIR__.'/db.conf';
   $md5 = md5_file($file);
   while (true){
       $md5_check =  md5_file($file);
       if(strcmp($md5,$md5_check)){
           echo '文件被修改'.date('Y-m-d H:i:s').PHP_EOL;
       }
       sleep(1);
   }
});
$child->start();

echo '当前进程名称:'.cli_get_process_title().PHP_EOL;
Process::wait();

/*
Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        var_dump($ret);
    }
});
?

07场景练习mysql简易巡检监控

!mysql监控 1、连接是否正常I 2、查询连接数 3、查询线程使用情况则进程能刚知到,然后发送什么消息等等

这种功能是很适合用多进程监控来做的 注意:今天的代码需要把swoole升级到4.3.X Peel install swoole 即可

监控:

select count(*) as c from information_schema.processlist
select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'Thread%'
select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'Abort%'
select * from information_schema.processlist

进程里面运行协程代码

<?php
use \Swoole\Process;
use \Swoole\Coroutine\MySQL;
echo '当前进程ID:'.posix_getpid().PHP_EOL;
cli_set_process_title('swoole-main');
$child =  new Process(function() {
    $swoole_mysql = new MySQL();
    $swoole_mysql->connect([
        'host' => '192.168.33.10',
        'port' => 3306,
        'user' => 'root',
        'password' => 'root',
        'database' => 'information_schema',
    ]);
    $checkConnect = "select 1";
    $checkProcessCount = "select count(*) as c from information_schema.processlist";
    $checkThread = "select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'Thread%'";
    //$res = $swoole_mysql->query('select 1'); //测试
    while (true){
        $checkResult[] = date('Y-m-d H:i:s');
        try{
            $swoole_mysql->query($checkConnect);
            $checkResult[] = '检查连接正常';
            $res = $swoole_mysql->query($checkProcessCount);
            $checkResult[] = '当前连接数'.$res[0]['c'];
            $res = $swoole_mysql->query($checkThread);
            $checkResult[] = '检查线程情况';
            foreach ($res as $row){
                foreach ($row as $key => $value){
                    $checkResult[] = $key.':'.$value;
                }
            }
            $checkResult[] = '---------------------';
            echo implode(PHP_EOL,$checkResult);
        }catch (Exception $exception){
            echo $exception->getMessage().PHP_EOL;
        }
       sleep(5);
    }
},false,0,true);
$child->start();
echo '当前进程名称:'.cli_get_process_title().PHP_EOL;
Process::wait();
/*
Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        var_dump($ret);
    }
});
*/

08场景练习 多进程监控订单表状态、父子进程通信

Process->write

向管道内写入数据。

function Process->write(string $data) int | bool;
  • 在子进程内调用write,父进程可以调用read接收此数据
  • 在父进程内调用write,子进程可以调用read接收此数据

测试数据

<?php
use \Swoole\Process;
use \Swoole\Coroutine\MySQL;
echo '当前进程ID:'.posix_getpid().PHP_EOL;
cli_set_process_title('swoole-main');

/**
CREATE TABLE `zerg`.`swoole_order`  (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '表ID',
`order_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订单号',
`is_pay` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否支付',
`is_notice` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否通知',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT  CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
 */
$child =  new Process(function() {
    $swoole_mysql = new MySQL();
    $swoole_mysql->connect([
        'host' => '192.168.33.10',
        'port' => 3306,
        'user' => 'root',
        'password' => 'root',
        'database' => 'zerg',
    ]);
    while (true){
        try{
            $test = 'insert into swoole_order (order_no) value ("'.md5(time()).'")';
            $swoole_mysql->query($test);

            var_dump($swoole_mysql);

        }catch (Exception $exception){
            echo $exception->getMessage().PHP_EOL;
        }
       sleep(1);
    }
},false,0,true);
$child->start();
echo '当前进程名称:'.cli_get_process_title().PHP_EOL;

Process::wait();
/*
Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        var_dump($ret);
    }
});
*/

案例代码:父子进程通信,子进程之间通信

<?php
use \Swoole\Process;
use \Swoole\Coroutine\MySQL;
echo '当前进程ID:'.posix_getpid().PHP_EOL;
cli_set_process_title('swoole-main');

//该子进程负责查询已支付并未通知订单信息
$child_select =  new Process(function(Process $process) {
    $swoole_mysql = new MySQL();
    $swoole_mysql->connect([
        'host' => '192.168.33.10',
        'port' => 3306,
        'user' => 'root',
        'password' => 'root',
        'database' => 'zerg',
    ]);
    while (true){
        try{
            $order = "select id,order_no,is_pay,is_notice from swoole_order where is_pay=1 and is_notice=0 limit 1";
            $res = $swoole_mysql->query($order);
            if($res && count($res)){
                $process->write($res[0]['order_no']);
            }
        }catch (Exception $exception){
            echo $exception->getMessage().PHP_EOL;
        }

       sleep(3);
    }
},false,1,true); //1:创建SOCK_STREAM类型管道
$child_select->start();

//该子进程负责对已支持订单发送通知
$child_notice =  new Process(function(Process $process) {
    while (true){
        $order_notice = $process->read();
        if($order_notice){
            echo '进程2获取到订单号:'.$order_notice.PHP_EOL;
        }
        usleep(0.5 *1000 * 1000); //微秒
    }

},false,1,true); //1:创建SOCK_STREAM类型管道
$child_notice->start();

//主进程接收子进程的消息后往另一个子进程写消息
while (true){
    $order_no = $child_select->read();
    if($order_no){
        $child_notice->write($order_no);
    }
    usleep(0.5 *1000 * 1000); //微秒
}

echo '当前进程名称:'.cli_get_process_title().PHP_EOL;

Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        var_dump($ret);
    }
});

09场景练习 多进程监控订单表状态、队列通信

使用队列通信

<?php
use \Swoole\Process;
use \Swoole\Coroutine\MySQL;
echo '当前进程ID:'.posix_getpid().PHP_EOL;
cli_set_process_title('swoole-main');

//该子进程负责查询已支付并未通知订单信息
$child_select =  new Process(function(Process $process) {
    $swoole_mysql = new MySQL();
    $swoole_mysql->connect([
        'host' => '192.168.33.10',
        'port' => 3306,
        'user' => 'root',
        'password' => 'root',
        'database' => 'zerg',
    ]);
    $offset = 0;
    while (true){
        try{
            $order = "select order_no from swoole_order where is_pay=1 and is_notice=0 limit {$offset},1";
            $res = $swoole_mysql->query($order);
            if($res && count($res)){
                $process->push($res[0]['order_no']);
            }
        }catch (Exception $exception){
            echo $exception->getMessage().PHP_EOL;
        }
        $offset++;
       sleep(3);
    }
},false,1,true); //1:创建SOCK_STREAM类型管道
$child_select->useQueue(2);
$child_select->start();

//该子进程负责对已支持订单发送通知
$child_notice1 =  new Process(function(Process $process) {
    while (true){
        $order_notice = $process->pop();
        if($order_notice){
            echo '1子进程从消息队列取消息:'.$order_notice.PHP_EOL;
        }
        usleep(0.5 *1000 * 1000); //微秒
    }

},false,1,true); //1:创建SOCK_STREAM类型管道
$child_notice1->useQueue(2);
$child_notice1->start();


//该子进程负责对已支持订单发送通知
$child_notice2 =  new Process(function(Process $process) {
    while (true){
        $order_notice = $process->pop();
        if($order_notice){
            echo '2子进程从消息队列取消息:'.$order_notice.PHP_EOL;
        }
        usleep(0.5 *1000 * 1000); //微秒
    }

},false,1,true); //1:创建SOCK_STREAM类型管道
$child_notice2->useQueue(2);
$child_notice2->start();

/**
 * 思路:
 * 1、进程之间消息不会发生同时争夺
 * 2、如果发送通知比较耗时,没有处理完又从数据库中读取消息 -> 考虑使用redis锁实现
 */

echo '当前进程名称:'.cli_get_process_title().PHP_EOL;

Process::wait();
/*
Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        var_dump($ret);
    }
});
*/

10调用外部程序成为子进程之调用Go写的程序、设置进程名称

  • 完整路径/usr/bin/php xx.php$(which php) xx.php/usr/bin/env php xx.php

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

bool Process->exec(string $execfile, array $args)
  • $execfile指定可执行文件的绝对路径,如 "/usr/bin/python"
  • $args是一个数组,是exec的参数列表,如 array('test.py', 123),相当于python test.py 123

执行成功后,当前进程的代码段将会被新程序替换。子进程蜕变成另外一套程序。父进程与当前进程仍然是父子进程关系。

父进程与新进程之间可以通过标准输入输出进行通信,必须启用标准输入输出重定向。

$execfile必须使用绝对路径,否则会报文件不存在错误 由于exec系统调用会使用指定的程序覆盖当前程序,子进程需要读写标准输出与父进程进行通信 如果未指定redirect_stdin_stdout = true,执行exec后子进程与父进程无法通信

$process = new \Swoole\Process(function (\Swoole\Process $childProcess) {
    // 不支持这种写法
    // $childProcess->exec('/usr/local/bin/php /var/www/project/yii-best-practice/cli/yii 
    t/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(); // 启动子进程

父进程与exec子进程使用管道进行通信:

// exec - 与exec进程进行管道通信
use Swoole\Process;
$process = new Process(function (Process $worker) {
    $worker->exec('/bin/echo', ['hello']);
    $worker->write('hello');
}, true); // 需要启用标准输入输出重定向
$process->start();
echo "from exec: ". $process->read(). "\n";
<?php

# 案例
  
use \Swoole\Process;
echo '当前进程ID:'.posix_getpid().PHP_EOL;
cli_set_process_title('swoole-main');

$process = new Process(function (Process $p){
    echo '123'; //该代码段将会被新程序替换,不会执行
    $p->exec('/usr/bin/php',[__DIR__.'/run.php','name']);
    echo 'abc'; //该代码段将会被新程序替换,不会执行
},true,0,true);  // 父子进程进行管道通信,需要启用标准输入输出重定向
$process->start();

while (true){
    $str = $process->read();
    echo $str;
}

Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
       // var_dump($ret);
    }
});
<?php
# run.php
cli_set_process_title('swoole-exec');
while (true){
    echo date('Y-m-d H:i:s').PHP_EOL;
    sleep(3);
}

[root@localhost ~]# ps -ef | grep swoole root 3494 3064 0 01:00 pts/0 00:00:00 swoole-main root 3495 3494 0 01:00 pts/0 00:00:00 swoole-exec

11(附加课)调用外部程序成为子进程之调用Go写的程序、设置进程名称

$process = new Process(function (Process $p){
    echo '123'; //该代码段将会被新程序替换,不会执行
    $p->exec('/usr/run.go',[]);
    echo 'abc'; //该代码段将会被新程序替换,不会执行
},true,0,true);  // 父子进程进行管道通信,需要启用标准输入输出重定向
$process->start();

12易进程管理器(1)读取配置、启动多个子进程

#pm.conf
[child]
send = /usr/bin/env php send.php
move = /usr/bin/env php move.php
<?php
//function.php
use \Swoole\Process;

function init(){
    $config = parse_ini_file('pm.conf',true);
    $child = $config['child'];
    foreach ($child as $name => $item){
        $params = explode(' ',$item);
        $process = new Process(function (Process $p) use($params){
            $p->exec($params[0],array_splice($params,1));
        });
        $process->start();
    } 
}

//查看linux下的信号列表 kill -l
<?php
//move.php
cli_set_process_title('swoole-move');
while (true){
    echo 'move-data'.PHP_EOL;
    sleep(3);
}
<?php
//pm.php 进程入口
use \Swoole\Process;
echo '当前进程ID:'.posix_getpid().PHP_EOL;
cli_set_process_title('swoole-main');

//包含公共函数
require  'function.php';

//初始化进程
init();

//在异步信号回调中执行wait
Process::signal(SIGCHLD, function($sig) {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        //执行回收后的处理逻辑,比如拉起一个新的进程
        var_dump($ret);
    }
});

//回收结束运行的子进程。
Process::wait(true);
<?php
//send.php
cli_set_process_title('swoole-send');
while (true){
    echo 'send-data'.PHP_EOL;
    sleep(3);
}

13简易进程管理器(2)监控配置文件、支持动态添加子进程

<?php
//pm.php
use \Swoole\Process;
echo '当前进程ID:'.posix_getpid().PHP_EOL;
cli_set_process_title('swoole-main');

//包含公共函数
require  'function.php';

//核心进程
intCore();

//启动进程
init();

//主进程拦截子进程发送信号
Process::signal(SIGUSR1, function() {
    echo '进程正在重新配置'.date('Y-m-d H:i:s').PHP_EOL;
    init();
});


//在异步信号回调中执行wait
//查看linux下的信号列表 kill -l
Process::signal(SIGCHLD, function() {
    //必须为false,非阻塞模式
    while($ret =  Process::wait(false)) {
        //执行回收后的处理逻辑,比如拉起一个新的进程
        var_dump($ret);
    }
});


//回收结束运行的子进程。
//Process::wait(true);
<?php
//function.php
use \Swoole\Process;

//创建进程
$processList = []; //主进程变量,管理进程变量
function init(){
    global $processList; //
    $config = parse_ini_file('pm.conf',true);
    $child = $config['child'];
    foreach ($child as $name => $item){
        $params = explode(' ',$item);
        $process = new Process(function (Process $p) use($params){
            $p->exec($params[0],array_splice($params,1));
        });
        $pid = $process->start();//用户自定义进程
        $processList[$name] = [
            'pid' =>$pid,
            'date' =>date('Y-m-d H:i:s'),
        ]; ;
    }
}

//运行 核心子进程
function intCore(){
    $process = new Process(function (Process $p) {
        cli_set_process_title('swoole-watch');
        watchConfig(); //运行子进程,子父进程通信发送信号
    });
    $process->start();
}

//监听文件
function watchConfig(){
    $child =  new Process(function() {
        $file = __DIR__.'/pm.conf';
        $md5 = md5_file($file);
        while (true){
            $md5_check =  md5_file($file);
            if(strcmp($md5,$md5_check)){

                //子进程和父进程通信,发送信号
                Process::kill(posix_getppid(),SIGUSR1); //向父进程发送账户

                echo posix_getppid().'文件被修改'.date('Y-m-d H:i:s').PHP_EOL;
            }
            sleep(3);
        }
    });
    $child->start();
}

14简易进程管理器(2)监控配置文件、支持动态删除子进程

<?php
//function.php
use \Swoole\Process;

//创建进程
$processList = []; //主进程变量,管理进程变量
function init(){
    global $processList; //
    $config = parse_ini_file('pm.conf',true);
    $child = $config['child'];
    foreach ($child as $name => $item){
        $params = explode(' ',$item);
        $process = new Process(function (Process $p) use($params){
            $p->exec($params[0],array_splice($params,1));
        });
        $pid = $process->start();//用户自定义进程
        $processList[$name] = [
            'pid' =>$pid,
            'date' =>date('Y-m-d H:i:s'),
        ]; ;
    }
    //删除进程
    rmProcess($child);
}

//运行 核心子进程
function intCore(){
    $process = new Process(function (Process $p) {
        cli_set_process_title('swoole-watch');
        watchConfig(); //运行子进程,子父进程通信发送信号
    });
    $process->start();
}

//监听文件
function watchConfig(){
    $child =  new Process(function() {
        $file = __DIR__.'/pm.conf';
        $md5 = md5_file($file);
        while (true){
            $md5_check =  md5_file($file);
            if(strcmp($md5,$md5_check)){

                //子进程和父进程通信,发送信号
                Process::kill(posix_getppid(),SIGUSR1); //向父进程发送信号

                echo posix_getppid().'文件被修改'.date('Y-m-d H:i:s').PHP_EOL;
            }
            sleep(3);
        }
    });
    $child->start();
}



function rmProcess($child){
    global $processList;
    $process = array_diff_key($processList,$child);
    foreach ($process as $pkey => $pvalue){
        Process::kill($pkey['pid'],SIGTERM);
        unset($processList[$pkey]);
    }
}

15简易进程管理器支持API查看子进程运行情况

<?php

//使用文件共享进程状态
$http = new Swoole\Http\Server('0.0.0.0', 9501);
$http->set([
    'worker_num'    => 1
]);
$http->on('Request', function ($request, $response) {
    $response->header('Content-Type', 'text/html; charset=utf-8');
    $content = file_get_contents('pm.status');
    $response->end($content);
});

$http->start();
function httpWeb(){
    $process = new Process(function (Process $p){
        $p->exec('/usr/bin/env',['php',__DIR__.'/http.php']);
    });
    $pid = $process->start();//用户自定义进程
}

16简易进程管理器 子进程退出时修改状态、API状态同步

17简易定时任务(1)crontab表达式解析、定时执行函数

20补充课时swoole 4.4.x之后子进程回收代码的问题处理

Process主进程自动退出 https://github.com/swoole/swoole-src/issues/2731

4.4版本开始底层将不再将信号监听作为 EventLoop Exit 的 block 条件。有两个解决方案:

  1. 添加一个 tick 定时器
  2. 改为阻塞等待进程退出
use Swoole\Process;
cli_set_process_title("master");
$manager=new Process(function(Process $process){
    cli_set_process_title("manager");
    while(true) {
        echo "a".PHP_EOL;
        sleep(1);
    }
});

   $ret =  Process::wait();
        echo "PID={$ret['pid']}\n";



相关推荐

“版本末期”了?下周平衡补丁!国服最强5套牌!上分首选

明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...

VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符&quot;

首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...

东营交警实名曝光一批酒驾人员名单 88人受处罚

齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...

Qt界面——搭配QCustomPlot(qt platform)

这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...

大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写

老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...

测试谷歌VS Code AI 编程插件 Gemini Code Assist

用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...

顾爷想知道第4.5期 国服便利性到底需优化啥?

前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...

掌握Visual Studio项目配置【基础篇】

1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...

还嫌LED驱动设计套路深?那就来看看这篇文章吧

随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...

Visual Studio Community 2022(VS2022)安装图文方法

直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...

Qt添加MSVC构建套件的方法(qt添加c++11)

前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...

Qt为什么站稳c++GUI的top1(qt c)

为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...

qt开发IDE应该选择VS还是qt creator

如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...

Qt 5.14.2超详细安装教程,不会来打我

Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...

Cygwin配置与使用(四)——VI字体和颜色的配置

简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...

取消回复欢迎 发表评论: