在php中实现多进程,一般有两种方法,一种是使用PHP自带的pcntl_*函数(仅限linux),另一种就是使用popen/proc_open,然后在php内部控制进程数量。
使用pcntl_*函数
PHP提供了一系列的pcntl_*函数,顾名思义就是process control functions,专门用来管理进程的。最常用的就是pcntl_fork和pcntl_wait。
pcntl_fork的作用就是从当前的进程再派生出一个子进程。pcntl_wait的作用是挂起当前进程,直到一个子进程中止。
例子:
复制代码 代码示例:
<?php
//配合pcntl_signal使用
declare(ticks=1);
//最大的子进程数量
$max = 5;
//当前的子进程数量
$child = 0;
//当子进程退出时,会触发该函数
function sig_handler($sig) {
global $child;
switch($sig) {
case SIGCHLD:
echo 'SIGCHLD received'."n";
$child--;
}
}
//注册子进程退出时调用的函数
pcntl_signal(SIGCHLD, "sig_handler");
while(true) {
$child++;
/**
* 这个函数会返回两个值,一个为0,表示子进程;一个为正整数表示子进程的id
* 所以if和else里的两段代码都会执行
* if里的代码是父进程执行的
* else里的代码是子进程执行的
*/
$pid = pcntl_fork();
if ($pid) {
//这里是父进程执行的代码
//如果子进程数超过了最大值,则挂起父进程
//也就是说while语句不会继续执行
if ($child >= $max) {
pcntl_wait($status);
}
}
else {
//这里是子进程执行的代码
//如果要执行其他命令的话,使用pcntl_exec
echo "starting new child | now we have $child child processn";
sleep(rand(3, 5));
exit;
}
}
?>
上面这段代码就是保证有5个子进程一直在干活,如果$child数量大于$max,就等子进程结束后再继续运行。子进程结束后会调用 sig_handler函数,sig_handler会将$child
数量减1,那边while继续执行。
使用popen/proc_open
popen会创建一个管道来连接该进程,然后使用fread/fgets/stream_get_contents来读取该进程返回的结果。跟 exec或system之类的函数不同的是,exec会等待命令执行完
成,再运行下面的代码,但popen不会。proc_open又更加强大一些,支持 stdin和stdout,路径设置等等。
因为这些函数只负责创建,没有相应的管理方法,所以只能在php文件内部自己来实现。
演示示例:(PHP多进程并发控制的测试实例)
复制代码 代码示例:
<?php
function run($input)
{
global $p_number;
if ($p_number <= 0)
{
$p_number = worker_processes($p_number);
}
$p_number = $p_number - 1;
$out = popen("/bin/sh /opt/zhangyan.sh "{$input}" &", "r");
pclose($out);
}
function worker_processes($p_number)
{
$limit = 500;//允许推到后台的最大进程数
while ($p_number <= 0)
{
$cmd = popen("ps -ef | grep "/opt/zhangyan.sh" | grep -v grep | wc -l", "r");
$line = fread($cmd, 512);
pclose($cmd);
$p_number = $limit - $line;
if ($p_number <= 0)
{
sleep(1);//暂停1秒钟
}
}
return $p_number;
}
$input = "http://www.jb200.com"; //模拟从队列文件中读取到的数据
for ($i = 1; $i <= 1000; $i++)
{
run($input);
echo "Idle process number: " . $p_number . "n";
}
?>
代码说明:
1. 设置/opt/zhangyan.php最多允许生成500个子进程;
2. 当/opt/zhangyan.php读取到一条数据后,将允许生成的子进程数减1(空闲进程数$p_number=500-1=499),然后将数据交给/opt/zhangyan.sh去后台处理,不等待
/opt/zhangyan.sh处理结束,继续读取下一条数据;
3. 当允许生成的子进程数减至0时(空闲进程数$p_number=0),/opt/zhangyan.php会等待1秒钟,然后检查后台还有多少个/opt /zhangyan.sh子进程尚未处理结束;
4. 如果1秒钟之后/opt/zhangyan.php发现后台的/opt /zhangyan.sh子进程数还是500(空闲进程数$p_number=0),会继续等待1秒钟,如此反复;
5. 如果/opt /zhangyan.php发现后台尚未处理结束的/opt/zhangyan.sh子进程数减少到300个了(空闲进程数$p_number=500-300=200),那么/opt/zhangyan.php会再往后台
推送200个/opt/zhangyan.sh子进程;
建议使用pcntl_*系函数更方便一些,逻辑更清楚。