PHP多进程(进程控制扩展pcntl)

PHP是一门较早出现的WEB开发脚本语言,并由于其语法结构简单、易学、开源等特性迅速占领WEB开发脚本语言领域,并成为这个领域的龙头老大直至今日。PHP从一出生就被设计用来快速开发WEB应用,这也注定了它在某些方面的先天不足,例如在cli环境下处理大量数据的情况,或者在并发编程方面,都显得力不从心。

本文主要讲解基于PCNTL的PHP并发编程,虽然PHP本身不支持多进程,但基于Linux的PHP扩展PCNTL却可以提供多进程编程。网络上很多同类文章,但笔者进行多次尝试后发现,不是难以控制进程数量,就是有潜在产生僵尸进程或孤儿进程的危险,或者父进程阻塞难以获得更大的并发效果,且大多没有介绍FORK的原理,使得PHP程序员学习PCNTL并发编程尤为困难。本文力求解决这个问题。

FORK编程的大概原理是,每次调用fork函数,操作系统就会产生一个子进程,儿子进程所有的堆栈信息都是原封不动复制父进程的,而在fork之后,父进程与子进程实际上是相互独立的,父子进程不会相互影响。也就是说,fork调用位置之前的所有变量,父进程和子进程是一样的,但fork之后则取决于各自的动作,且数据也是独立的;因为数据已经完整的复制给了子进程。而唯一能够区分父子进程的方法就是判断fork的返回值。如果为0,表示是子进程,如果为正数,表示为父进程,且该正数为子进程的PID(进程号),而如果是-1,表示子进程创建失败

提个伪需求:顺序向一个文件输出2000000个数字0-1999999, 每个数字一行

常规思路:

1
2
3
4
5
6
<?php
$max = 2000000
$logFile = '/tmp/pcntl.log';
for ($i = 0; $i < $max; $i++) { //循环向文件写入数字
file_put_contents($logFile, $i . PHP_EOL, FILE_APPEND);
}

多进程(pcntl扩展):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
$max = 2000000;
$workers = 10;
$logFile = '/tmp/pcntl.log';
pcntl_signal(SIGCHLD, SIG_IGN);
for ($i = 0; $i < $workers; $i++) {
$pid = pcntl_fork(); //创建子进程
if ($pid == -1) {
die('could not fork'); //错误处理:创建子进程失败时返回-1
}
if ($pid) {
//父进程会得到子进程号,所以这里是父进程执行的逻辑
//如果不需要阻塞进程,而又想得到子进程的退出状态,则可以注释掉pcntl_wait($status)语句
pcntl_wait($status, WNOHANG); //等待子进程中断,防止子进程成为僵尸进程。
} else {
//子进程得到的$pid为0, 所以这里是子进程执行的逻辑 执行处理逻辑
$lastId = $i === 0 ? 0 : $max / $workers * $i;
$maxId = $max / $workers * ($i + 1) - 1;
for ($j = $lastId; $j <= $maxId; $j++) {
file_put_contents($logFile, $j . PHP_EOL, FILE_APPEND);
}
exit;
}
}

time php pcntl.php
执行时间:
1、常规思路: php pcntl.php 2.24s user 4.56s system 97% cpu 6.981 total
2、多进程: php pcntl.php 0.02s user 0.00s system 23% cpu 0.086 total