一、前言
我们都知道PHP是单线程执行,处理多并发主要是依赖服务器或PHP-FPM的多进程及它们进程的复用,但PHP实现多进程也意义重大,尤其是在后台Cli模式下处理大量数据或运行后台DEMON守护进程时。不能应用在Web服务器环境。
/** 检测是否CLI模式,确保这个函数只能运行在SHELL中 */ if (substr(php_sapi_name(), 0, 3) !== 'cli') { die("cli mode only"); }
日常任务中,有时需要通过php脚本执行一些日志分析,队列处理等任务,当数据量比较大时,可以使用多进程来处理。
PHP的多线程也曾被人提及,但进程内多线程资源共享和分配的问题难以解决。PHP也有多线程想关的扩展 pthreads ,但据说不太稳定,且要求环境为线程安全,所用不多。
要实现PHP的多进程,需要安装 pcntl 和 posix 扩展。
二、创建子进程
使用 pcntl_fork() 函数可以在当前位置产生分支。fork 是创建了一个子进程,父进程和子进程都从 fork 的位置开始向下继续执行,不同的是父进程执行过程中,得到的 fork 返回值为子进程号,而子进程得到的是0,执行失败则返回-1。
因为系统初始init进程的pid为1,后来的所有进程pid都会大于该进程,所以可以通过 pcntl_fork() 的返回值大于1来判断当前进程是父进程,返回值等于0来判断是子进程。
$ppid = posix_getpid(); // 获取当前进程的id $pid = pcntl_fork(); // 创建子进程 if ($pid == -1) { throw new Exception('fork子进程失败!'); } elseif ($pid > 0) { // 父进程执行逻辑 cli_set_process_title("我是父进程,我的进程id是{$ppid}."); sleep(30); pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。 } else { // 子进程执行逻辑 $cpid = posix_getpid(); cli_set_process_title("我是{$ppid}的子进程,我的进程id是{$cpid}."); sleep(30); }
执行结果:
注意:如果是在循环中创建子进程,那么子进程中最后要 exit 退出,防止子进程进入循环。
三、管理子进程
管理子进程,使用的是信号。简单来说,就是父进程里使用两个函数 pcntl_signal() 和 pcntl_signal_dispatch(),负责给子进程安装信号处理器和分发工作。
在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。
我们通过在父进程接收子进程传来的信号,判断子进程状态,来对子进程进行管理。我们需要在父进程里使用 pcntl_signal() 函数和 pcntl_signal_dispatch() 函数来给各个子进程安装信号处理器:
// 安装一个信号处理器,$signo是待处理的信号常量,callback是其处理函数 pcntl_signal (int $signo , callback $handler) // 调用每个等待信号通过pcntl_signal()安装的处理器 pcntl_signal_dispatch ()
PHP内常见的信号常量有:
- SIGCHLD:子进程退出成为僵尸进程会向父进程发送此信号
- SIGHUP:进程挂起
- SIGTEM:进程终止
四、处理子进程
处理子进程,需要两个函数:
// 向进程id为$pid的进程发送$sig信号 bool posix_kill ( int $pid, int $sig ) //挂起当前进程的执行直到进程号为$pid的进程退出(如果$pid为-1,则等待任意一个子进程) int pcntl_waitpid ( int $pid, int &$status [, int $options = 0 ] )
posix_kill() 函数通过向子进程发送一个信号来操作子进程,在需要要时可以选择给子进程发送进程终止信号来终止子进程;
pcntl_waitpid() 函数等待或返回 fork 的子进程状态,如果指定的子进程在此函数调用时已经退出(俗称僵尸进程),此函数将立刻返回,并释放子进程的所有系统资源,此进程可以避免子进程变成僵尸进程,造成系统资源浪费。这样就可以实现跟子进程共同完成的任务的目的了。
五、实例
如果一个任务被分解成多个进程执行,就会减少整体的耗时。比如有一个比较大的数据文件要处理,这个文件由很多行组成。如果单进程执行要处理的任务,量很大时要耗时比较久。这时可以考虑多进程。多进程处理分解任务,每个进程处理文件的一部分,这样需要均分割一下这个大文件成多个小文件(进程数和小文件的个数等同就可以)。
比如文件 file.log 有10万行数据,现在想分4个进程处理。需要分割2.5万行一个文件。命令 split 可以做到:
<?php shell_exec('split -l 25000 -d file.log prefix_name'); // 3个子进程处理任务 for ($i = 0; $i < 3; $i++){ $pid = pcntl_fork(); if ($pid == -1) { die("could not fork"); } elseif ($pid) { echo "I'm the Parent $i\n"; } else {// 子进程处理 $content = file_get_contents("prefix_name0".$i); // 业务处理 begin // 业务处理 end exit; // 一定要注意退出子进程,否则pcntl_fork()会被子进程再fork,带来处理上的影响。 } } // 等待子进程执行结束 while (pcntl_waitpid(0, $status) != -1) { $status = pcntl_wexitstatus($status); echo "Child $status completed\n"; }
参考:
《php多进程总结》:https://www.cnblogs.com/leezhxing/p/5223289.html
《初探PHP多进程》:https://www.cnblogs.com/zhenbianshu/p/5676822.html
《PHP利用多进程处理任务》:https://www.cnblogs.com/firstForEver/p/7301630.html
来源:https://www.cnblogs.com/tangxuliang/p/9208133.html