5

php的多进程 - Limboy's HQ

 3 years ago
source link: https://limboy.me/2010/08/27/php-process-control/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

php的多进程

2010-08-27

一般有两种方法,一种是使用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 process\n";
		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文件内部自己来实现。 demo(引用自张宴——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://blog.s135.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_*系函数更方便一些,逻辑也更清楚。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK