V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
leven87
V2EX  ›  PHP

PHP 多进程的困惑

  •  
  •   leven87 · 2018-04-23 23:49:57 +08:00 · 4594 次点击
    这是一个创建于 2408 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在研究 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 扩展。
    9 条回复    2018-04-25 11:12:06 +08:00
    leven87
        1
    leven87  
    OP
       2018-04-24 00:01:53 +08:00
    又查了一下资料,大概明白了,我这个多进程任务是 web 请求,属于 I/O 密集型任务,cpu 占用很少。时间瓶颈不在 cpu,所以多进程能够有效加快速度。
    msg7086
        2
    msg7086  
       2018-04-24 05:16:26 +08:00   ❤️ 5
    你提问里说对了一半。
    时间有两个指标,一个叫墙上钟时间,也就是现实中流逝的时间,另一个叫 CPU 时间,是 CPU 花费在执行程序的时间片的总和。
    使用多线程和多进程,会减少 Wall clock 时间,但会增加 CPU clock 时间。
    对于 CPU 密集型任务来说,单进程单线程效率更高。
    对于经常需要 CPU 等待的任务来说,多进程多线程可以增加 CPU 利用率,减少现实流逝时间。

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

    很多业务场景下对性能的要求并不是很高,多线程多进程并发的性能已经足够好的情况下,不会随便就去重写成回调的。
    leven87
        8
    leven87  
    OP
       2018-04-25 10:55:52 +08:00
    @msg7086 @vincenttone
    感谢。按照 @vincenttone 的建议,我试装了 ev 的扩展,还真是可以利用 EvTimer 做一个延时异步回调,不过如果 php 主程序执行完了,回调就 不会执行了。可能有更完善的方法,不过我没有去继续研究了。如同 @msg7086 所说,多进程和多线程已经可以满足大部分场景,使用这种回调对性能改善不会太多。php 也不擅长做这个,可能 python 在这方面的实现要更细腻一些。
    vincenttone
        9
    vincenttone  
       2018-04-25 11:12:06 +08:00   ❤️ 1
    @leven87 试试 EvIo 监听 io,不需要 timer。
    至于 php 和 python 的多线程,去看一下用户级线程和内核级线程;
    多进程,需要了解一下进程间通信。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5764 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 02:51 · PVG 10:51 · LAX 18:51 · JFK 21:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.