PHP 多进程的困惑

2018-04-23 23:49:57 +08:00
 leven87
最近在研究 php 多进程。看到普遍的观点是,多进程对于单核 cpu 没有什么作用。因为 cpu 同时只能运行一个线程,当然同时只能运行一个进程,那么进程调度的开销让你的多进程没有意义。 可是实际运行却发现,多进程比单进程耗时要小的多。请看代码:

<?php
class WebServer
{
private $list;
public function __construct()
{
$this->list = [];
}
public function worker($request){
$pid = pcntl_fork();
if($pid == -1){
return false;
}
if($pid > 0){
return $pid;
}
if($pid == 0){
$time = $request[0];
$method = $request[1];
$start = microtime(true);
echo getmypid()."\t start ".$method."\tat".$start.PHP_EOL;
//sleep($time);
$c = file_get_contents($method);
echo getmypid() ."\n";
$end = microtime(true);
$cost = $end-$start;
echo getmypid()."\t stop \t".$method."\tat:".$end."\tcost:".$cost.PHP_EOL;
exit(0);
}
}
public function master($requests){
$start = microtime(true);
echo "All request handle start at ".$start.PHP_EOL;
foreach ($requests as $request){
$pid = $this->worker($request);
if(!$pid){
echo 'handle fail!'.PHP_EOL;
return;
}
array_push($this->list,$pid);
}
while(count($this->list)>0){
foreach ($this->list as $k=>$pid){
$res = pcntl_waitpid($pid,$status,WNOHANG);
if($res == -1 || $res > 0){
unset($this->list[$k]);
}
}
usleep(100);
}
$end = microtime(true);
$cost = $end - $start;
echo "All request handle stop at ".$end."\t cost:".$cost.PHP_EOL;
}
}

$requests = [
[1,'http://www.sina.com'],
[2,'http://www.sina.com'],
[3,'http://www.sina.com'],
[4,'http://www.sina.com'],
[5,'http://www.sina.com'],
[6,'http://www.sina.com']
];

echo "多进程测试:".PHP_EOL;
$server = new WebServer();
$server->master($requests);

echo PHP_EOL."单进程测试:".PHP_EOL;
$start = microtime(true);
for($i=0;$i<6;$i++){
$c = file_get_contents("http://www.sina.com");
}
$end = microtime(true);
$cost = $end - $start;
echo "All request handle stop at ".$end."\t cost:".$cost.PHP_EOL;

-------------------------------------------------------------------------------------
测试结果如下:
多进程测试:
All request handle start at 1524498288.8329
17820 start http://www.sina.com at1524498288.8379
17821 start http://www.sina.com at1524498288.8383
17822 start http://www.sina.com at1524498288.8389
17823 start http://www.sina.com at1524498288.8392
17824 start http://www.sina.com at1524498288.8397
17825 start http://www.sina.com at1524498288.8407
17820 stop http://www.sina.com at:1524498289.5795 cost:0.7415988445282
17824 stop http://www.sina.com at:1524498289.5899 cost:0.7502818107605
17822 stop http://www.sina.com at:1524498289.5968 cost:0.75795722007751
17821 stop http://www.sina.com at:1524498289.6514 cost:0.81310606002808
17823 stop http://www.sina.com at:1524498289.6571 cost:0.81785297393799
17825 stop http://www.sina.com at:1524498289.6614 cost:0.820631980896
All request handle stop at 1524498289.6657 cost:0.83276891708374

单进程测试:
All request handle stop at 1524498293.6477 cost:3.9819581508636

可以看到,多进程比单进程耗时要小的多。我的服务器是阿里云上的 1G 内存,单核 cpu,不过是 64 位的,不知道这个是否有影响。麻烦 V 友解答,谢谢。运行代码,需要给 php 加上 pcntl 扩展。
4612 次点击
所在节点    PHP
9 条回复
leven87
2018-04-24 00:01:53 +08:00
又查了一下资料,大概明白了,我这个多进程任务是 web 请求,属于 I/O 密集型任务,cpu 占用很少。时间瓶颈不在 cpu,所以多进程能够有效加快速度。
msg7086
2018-04-24 05:16:26 +08:00
你提问里说对了一半。
时间有两个指标,一个叫墙上钟时间,也就是现实中流逝的时间,另一个叫 CPU 时间,是 CPU 花费在执行程序的时间片的总和。
使用多线程和多进程,会减少 Wall clock 时间,但会增加 CPU clock 时间。
对于 CPU 密集型任务来说,单进程单线程效率更高。
对于经常需要 CPU 等待的任务来说,多进程多线程可以增加 CPU 利用率,减少现实流逝时间。

更好的选择是事件回调模型,既可以并发多任务,又不需要多线程支持,结合了两者的优点,效率最高。
hxndg
2018-04-24 09:37:27 +08:00
@msg7086
补充个地方,进程分实时和非实时。多线程要考虑线程调度对缓存的耗损,所以需要设置亲和力
wentaoliang
2018-04-24 11:18:54 +08:00
@msg7086 请问下 php 如果不借助 swoole 是否能实现回调机制或者类似的异步机制
leven87
2018-04-24 11:48:02 +08:00
@msg7086 感谢回复。前面的都可以理解。最后一句提到的事件回调,有些困惑。众所周知,js 里面有大量的事件回调函数,比如给 click 事件绑定回调函数等。当然,php 里面也可以有类似的先给参数注册一些回调函数。然后根据实际参数,执行对应的回调函数的做法,通常利用 call_user_func_array。但是这些回调函数都是同步的。没有看出可以并发多任务。是否可以具体说说机制,谢谢。
vincenttone
2018-04-24 14:14:21 +08:00
1. 因为涉及到 io,所以可以在等待时做时间片切换,节省了时间
2. php 可以试试单进程配合 ev 扩展看看效果。
3. php 和回调和 js 的回调因为并发模型不同,并发效果也不同,具体可以参考 js 的事件模型,同时参考 ev 里的 epoll 或者 select
msg7086
2018-04-24 23:15:39 +08:00
@wentaoliang @leven87
PHP 很多年没玩了,据我所知 PHP 自己是很难支持这种结构的。
要玩事件回调不如去玩 JS。

很多业务场景下对性能的要求并不是很高,多线程多进程并发的性能已经足够好的情况下,不会随便就去重写成回调的。
leven87
2018-04-25 10:55:52 +08:00
@msg7086 @vincenttone
感谢。按照 @vincenttone 的建议,我试装了 ev 的扩展,还真是可以利用 EvTimer 做一个延时异步回调,不过如果 php 主程序执行完了,回调就 不会执行了。可能有更完善的方法,不过我没有去继续研究了。如同 @msg7086 所说,多进程和多线程已经可以满足大部分场景,使用这种回调对性能改善不会太多。php 也不擅长做这个,可能 python 在这方面的实现要更细腻一些。
vincenttone
2018-04-25 11:12:06 +08:00
@leven87 试试 EvIo 监听 io,不需要 timer。
至于 php 和 python 的多线程,去看一下用户级线程和内核级线程;
多进程,需要了解一下进程间通信。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/449282

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX