基于 Process 模块在 PHP 中实现多进程(二):进程间通信

上篇教程中,学院君给大家介绍了通过 Swoole 提供的 Process 模块可以在 PHP 中实现多进程编程,并且实现了一个简单的多进程 TCP 服务器,但是在该示例中,主进程和子进程之间并没有数据交互,如果主进程需要得到子进程的反馈,或者子进程需要接收来自主进程的数据,那么就需要进程间通信了。

好在 Swoole 的 Process 模块可以很好的支持进程间通信,在基于 Process 模块实现的 PHP 多进程应用中,可以通过管道通信(Unix Socket)和消息队列通信两种方式实现进程间通信。下面我们通过一些简单的实例演示如何通过这两种方式实现进程间通信。

管道通信

在 Swoole Process 模块中通过管道进行进程间通信很简单,只需要通过 read/write 从管道读取数据就好了,下面我们编写一个简单的示例代码,从主进程传递命令 php --version 到子进程,子进程执行之后,将结果返回给主进程打印出来:

<?php

$process = new swoole_process(function (\Swoole\Process $worker) {
    // 子进程逻辑
    // 通过管道从主进程读取数据
    $cmd = $worker->read();
    ob_start();
    // 执行外部程序并显示未经处理的原始输出,会直接打印输出
    passthru($cmd);
    $ret = ob_get_clean() ? : ' ';
    $ret = trim($ret) . ". worker pid:" . $worker->pid . "\n";
    // 将数据写入管道
    $worker->write($ret);
    $worker->exit(0);  // 退出子进程
});  // Process 构造函数第三个参数默认为 true,启用管道,如果第二个参数也设置为 true,则在子进程中可以通过 echo 将数据写入管道

// 启动进程
$process->start();
// 从主进程将通过管道发送数据到子进程
$process->write('php --version');
// 从子进程读取返回数据并打印
$msg = $process->read();
echo 'result from worker: ' . $msg;

我们将上述代码保存到 process/communication.php 文件中,然后在命令行执行这段代码:

php process/communication.php

输出如下:

表示无论是从主进程发送数据到子进程,还是从子进程发送数据到主进程都是 OK 的,在通过管道进行进程间通信时,需要注意,数据只能单向流动,即通过管道发送的数据只能由另一个进程读取,自己不能读取;另外,管道通信默认为同步阻塞模式,如果要实现异步通信,需要借助 swoole_event_add 函数将管道加入事件循环。

异步通信

比如我们可以像下面这样讲上述管道通信调整为异步通信:

<?php

$process = new swoole_process(function (\Swoole\Process $worker) {
    // 子进程逻辑
    swoole_event_add($worker->pipe, function ($pipe) use ($worker) {
        // 通过管道从主进程读取数据
        $cmd = $worker->read();
        ob_start();
        // 执行外部程序并显示未经处理的原始输出,会直接打印输出
        passthru($cmd);
        $ret = ob_get_clean() ? : ' ';
        $ret = trim($ret) . ". worker pid:" . $worker->pid . "\n";
        // 将数据写入管道
        $worker->write($ret);
        $worker->exit(0);  // 退出子进程
    });
    // 其它子进程逻辑
});  // Process 构造函数第三个参数默认为 true,启用管道,如果第二个参数也设置为 true,则在子进程中可以通过 echo 将数据写入管道

// 启动进程
$process->start();
// 从主进程将通过管道发送数据到子进程
$process->write('php --version');
// 从子进程读取返回数据并打印
$msg = $process->read();
echo 'result from worker: ' . $msg;

这样一来,其它子进程逻辑就不会同步阻塞于 $process->read(),可以立即执行了。

消息队列

除了管道之外,还可以通过消息队列实现进程间通信,我们可以将上述实现代码调整为通过消息队列进行通信,调整后的代码如下:

<?php

$process = new swoole_process(function (\Swoole\Process $worker) {
    // 子进程逻辑
    // 从消息队列读取数据
    $cmd = $worker-> pop();
    echo "Message from master process: " . $cmd . "\n";
    ob_start();
    // 执行外部程序并显示未经处理的原始输出,会直接打印输出
    passthru($cmd);
    $ret = ob_get_clean() ? : ' ';
    $ret = trim($ret) . ". worker pid:" . $worker->pid . "\n";
    // 将返回消息推送到消息队列
    $worker->push($ret);
    $worker->exit(0);  // 退出子进程
}, false, false);  // 关闭管道
// 调用 useQueue 表示使用消息队列进行进程间通信
// 消息队列与管道通信不能共存
// 第一个参数表示消息队里的 key,第二个参数表示通信模式,2 表示争抢模式
// 使用争抢模式进行通信时,哪个子进程先读取到消息先消费,因此无法实现与指定子进程的通信
// 消息队列不支持事件循环,因此引入了 \Swoole\Process::IPC_NOWAIT 表示以非阻塞模式进行通信
$process->useQueue(1, 2 | \Swoole\Process::IPC_NOWAIT);
// 从主进程将将命令推送到消息队列
$process->push('php --version');
// 从消息队列读取返回消息
$msg = $process->pop();
echo 'Message from worker process: ' . $msg;

// 启动子进程
$process->start();

swoole_process::wait();   // 要调用这段代码,否则子进程中的 push 或 pop 可能会报错

将上述代码保存到 process/queue.php 文件,然后在命令行执行这段代码:

php process/queue.php

输出如下:

表示主进程与子进程通信成功。

关于基于 Swoole Process 模块实现的多进程管理我们就简单介绍到这里,更多有关 Process 模块的方法调用和使用细节,请参考 Swoole 官方文档,下一篇教程,学院君将介绍如何在 Swoole 中实现进程池。

上一篇: 通过 Process 模块在 PHP 中实现多进程(一):简单的多进程 TCP 服务器实现

下一篇: 基于 Process\Pool 通过进程池实现数据库和 Redis 的持久连接