魏长东

weichangdong

pcntl_fork的使用

之前接触过pcntl_fork,好久没用了,最近拿来复习下。

class Signfork

{

	/**

     * 设置子进程通信文件所在目录

     * @var  string

     */

	private $tmp_path='/tmp/';



	/**

     * Signfork引擎主启动方法

	 * 1、判断$arg类型,类型为数组时将值传递给每个子进程;类型为数值型时,代表要创建的进程数.

	 * @param object $obj 执行对象

	 * @param string|array $arg 用于对象中的__fork方法所执行的参数

	 * 如:$arg,自动分解为:$obj->__fork($arg[0])、$obj->__fork($arg[1])...

	 * @return  array  返回   array(子进程序列=>子进程执行结果);

     */

	public function run($obj,$arg=1)

	{

		if(!method_exists($obj,'__fork'))

		{

			exit("Method '__fork' not found!");

		}



		if(is_array($arg))

		{

			$i=0;

			foreach($arg as $key=>$val)

			{

				$spawns[$i]=$key;

				$i++;

				$this->spawn($obj,$key,$val);

			}

			$spawns['total']=$i;

		}elseif($spawns=intval($arg))

		{

			for($i = 0; $i < $spawns; $i++) 

			{

				$this->spawn($obj,$i);

			}

		}

		else

		{

			exit('Bad argument!');

		}

		if($i>1000) exit('Too many spawns!');



		return $this->request($spawns);

	}



	/**

     * Signfork主进程控制方法

	 * 1、$tmpfile 判断子进程文件是否存在,存在则子进程执行完毕,并读取内容

	 * 2、$data收集子进程运行结果及数据,并用于最终返回

	 * 3、删除子进程文件

	 * 4、轮询一次0.03秒,直到所有子进程执行完毕,清理子进程资源

	 * @param  string|array $arg 用于对应每个子进程的ID

	 * @return  array  返回   array([子进程序列]=>[子进程执行结果]);

     */

	private function request($spawns)

	{

		$data=array();

		$i=is_array($spawns)?$spawns['total']:$spawns;

		for($ids = 0; $ids<$i; $ids++)

		{

			while(!($cid=pcntl_waitpid(-1, $status, WNOHANG)))usleep(30000);

		//	$tmpfile=$this->tmp_path.'sfpid_'.$cid;

			//$data[$spawns['total']?$spawns[$ids]:$ids]=file_get_contents($tmpfile);

			//unlink($tmpfile);

		}

		return true;

	}



	/**

     * Signfork子进程执行方法

	 * 1、pcntl_fork 生成子进程

	 * 2、file_put_contents 将'$obj->__fork($val)'的执行结果存入特定序列命名的文本

	 * 3、posix_kill杀死当前进程

	 * @param object $obj	待执行的对象

	 * @param object $i		子进程的序列ID,以便于返回对应每个子进程数据

	 * @param object $param 用于输入对象$obj方法'__fork'执行参数

     */

	private function spawn($obj,$i,$param=null)

	{

		if(pcntl_fork()===0)

		{

			$cid=getmypid();

			file_put_contents($this->tmp_path.'sfpid_'.$cid,$obj->__fork($param));

			posix_kill($cid, SIGTERM);

			exit;

		}

	}

}

class test

{

	function __fork($arg)

	{

		return file_get_contents($arg);

	}

}



$limit		=microtime(true);

$test		=new test();

$Signfork	=new Signfork();

$arg=array(

'http://yahoo.com',

'http://baidu.com',

'http://google.com',

'http://qq.com',

'http://163.com',

'http://sina.com'

);





$Signfork->run($test,$arg);

echo 'Run time:'.(microtime(true)-$limit); //Run time:1.7930409908295

//以下这些话,是这文章的评论,我觉得对理解多线程很有帮助。
//A:你这个其实不叫多进程,你相当于开了个新进程去执行file_get_contents,然后销毁,在fork个新进程去file_get_contents下一个网址。本质还是单进程

//B:你理解的多进程是什么样子的呢

//A:我所理解的多进程是父进程无需等子进程返回的结果,多个子进程同时压入后台执行

//B:我觉得你这样说的话,我是不是可以这样理解,既然是父进程产生子进程,那么也没必要什么等不等,看你自己的逻辑了,如果你要让他等就等,不等就不需要等,你如果需要他返回的结果,就让他返回好了,不需要,完全也没必要这样做的。你说的我可不可以这么理解呀

//A:是的,我是这样理解多进程的。但是根据你写的代码来看,就是一个父进程fork一个子进程执行任务,然后销毁,然后父进程在fork一个子进程,再执行任务,再销毁。这样感觉本质还是单进程.

?>

执行完,在/tmp下生成了
sfpid_29170
sfpid_29171
sfpid_29172
sfpid_29173
sfpid_29174
这么多文件,在执行这个脚本的时候,我
ps axf|grep pcntl_fork|grep -v 'grep'得到的结果是:
[root@bj-171-121 tmp]# ps axf|grep pcntl_fork|grep -v 'grep'
29168 pts/17   S+     0:00  |                       \_ php pcntl_fork.php
29170 pts/17   S+     0:00  |                           \_ php pcntl_fork.php
29174 pts/17   S+     0:00  |                           \_ php pcntl_fork.php

可见59168是主进程,也就是说是父进程,别的29170都是生成的子进程。

首先,PHP不能实现多线程,只能多进程。

其次,PHP要实现并行处理任务一般有以下两种方法:

1、利用PHP的PCNTL函数族,fork出多个子进程来处理。值得注意的是此时所有子进程共享全局global变量,需要谨慎使用

2、利用shell同时运行多个PHP进程,此时所有进程之间独立,不会共享任何变量