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

狂拽酷炫吊炸天:用 PHP 协程实现多任务协作

  •  
  •   darluc · 2016-07-01 01:00:37 +08:00 · 8060 次点击
    这是一个创建于 2847 天前的主题,其中的信息可能已经有所发展或是发生改变。

    PHP 5.5 中最重要的特性之一就是对协程( coroutine )和生成器( generator )的支持。生成器的特性已经由官方文档和许多博文(比如这一篇这一篇)讲解得很充分了。另一方面,协程受到的关注则较少。这是因为协程的功能相较而言更加强大,但却难以讲解。

    本文会使用协程实现一个任务调度器,以此帮助你理解协程的概念和用法。我会先用几个段落做一些介绍。如果你觉得你已经对生成器和协程的基本概念掌握得很牢固了,那么你可以直接跳至“多任务协作”这一段开始阅读。

    生成器

    生成器背后最原始的想法就是一个函数不仅仅返回一次数据,而是能够返回一系列的数据,并且这些数据是挨个返回的。也可以理解为,生成器使你能更方便地实现迭代器。xrange() 函数就是一个生成器的简单例子:

    function xrange($start, $end, $step = 1) {
      for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
      }
    }
    
    foreach (xrange(1, 1000000) as $num) {
      echo $num, "\n";
    }
    

    上例中的 xrange() 函数与内置函数 range() 函数的功能相同。唯一的区别在于 range() 会返回一个包含了一百万个数字的数组,而 xrange() 则返回一个可以吐出这些数字的迭代器,不会去老实地计算出一个包含所有数字的数组。

    这样做的好处是显而易见的。它使得你可以处理超大规模的数据,而无需一次性将它们载入内存。你甚至可以处理无穷无尽的数据流。

    当然并不是只有生成器能做到这一点,你也可以通过实现一个 iterator 接口来完成同样的工作。生成器只是更加方便,避免你为了生成一个迭代器而不得不去实现该接口的五个不同方法。

    将生成器用作可中断函数

    要从对生成器的理解过度到协程的概念,理解它们内部的工作方式是非常重要的:生成器是可中断的函数,而 yield 语句则构成了这些中断点。

    接着刚才的例子,当你调用 xrange(1, 1000000) 时,实际上 xrange() 没有执行任何代码。取而代之地, PHP 仅返回了一个 Generator 类的实例,它实现了 Iterator 接口:

    $range = xrange(1, 1000000);
    var_dump($range); // object(Generator)#1
    var_dump($range instanceof Iterator); // bool(true)
    

    只有当你调用 iterator 接口相关的方法时代码才会执行。例如,你执行 $range->rewind() 时,xrange() 函数中的代码就会执行,直到流程中的第一条 yield 语句。如此一来,就意味着 $i = $startyield $i 被执行了。任何传递给 yield 语句的数据都能通过 $range->current() 来获取。

    你需要调用 $range->next() 方法来继续执行生成器中的代码。这样它就会继续执行下去,直到下一条 yield 语句。所以只要连续地调用 ->next()->current() 方法,你就可以从生成器中获取到所有的返回值,直至最终不再遇到 yield 语句。对于 xrange() 函数来说,就是 $i 超出 $end 的时候。如此一来,流程会继续执行完剩余的代码,直至函数的结尾。若此时调用 ->valid() 方法则会返回 false ,这个迭代过程就结束了。

    协程

    相对于上述功能,协程最主要的一点就是加入了向生成器中发送数据的能力。这使得从生成器到调用者的单向数据流,变成了两者彼此往来的数据通路。

    将数据传递给协程的方法是调用 ->send() 方法,而不是 ->next()。下面的这个 logger() 的例子展示了它是如何工作的:

    function logger($fileName) {
      $fileHandle = fopen($fileName, 'a');
      while (true) {
        fwrite($fileHandle, yield . "\n");
      }
    }
    
    $logger = logger(__DIR__ . '/log');
    $logger->send('Foo');
    $logger->send('Bar');
    

    如你所见,在这里 yield 没有被用作一个语句,而是作为一个表达式,也是就说它有一个返回值。这个返回值是通过 ->send() 语句传过来的。此例中 yield 会先返回 'Foo' 再返回 'Bar'。

    继续阅读请点击此处

    第 1 条附言  ·  2016-07-01 10:42:10 +08:00
    翻译自:[Cooperative multitasking using coroutines (in PHP!)]( https://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html)
    原文作者:[Nikita Popov]( https://nikic.github.io/aboutMe.html)
    第 2 条附言  ·  2016-07-01 11:47:10 +08:00
    突然发现大神翻译过了: http://www.laruence.com/2015/05/28/3038.html
    22 条回复    2016-07-01 22:26:45 +08:00
    eoo
        1
    eoo  
       2016-07-01 08:15:14 +08:00 via Android
    支持一个先
    hanyouchun66
        2
    hanyouchun66  
       2016-07-01 09:18:26 +08:00
    支持~
    JohnSmith
        3
    JohnSmith  
       2016-07-01 09:19:26 +08:00 via iPhone   ❤️ 1
    php 也是不容易,这样就要七个字
    pein
        4
    pein  
       2016-07-01 09:23:49 +08:00
    感觉很强大但是然并卵,应用场景不多吧。
    miaotaizi
        5
    miaotaizi  
       2016-07-01 09:33:09 +08:00
    @pein 小胡你又黑我 PHP
    somnus
        6
    somnus  
       2016-07-01 09:44:48 +08:00
    涨见识了
    darluc
        7
    darluc  
    OP
       2016-07-01 09:47:05 +08:00
    @pein 目前稍微有点用的是,看完后去看 Tencent Server Framework ( https://github.com/tencent-php/tsf ),会容易理解一些。其实有可能在此基础上产生一个不太一样的框架。
    fising
        8
    fising  
       2016-07-01 09:49:51 +08:00
    比起 go 的协程简直若爆了
    imcxy
        9
    imcxy  
       2016-07-01 09:58:11 +08:00   ❤️ 1
    PHP 想学好,比 C++都难。
    pein
        10
    pein  
       2016-07-01 10:05:55 +08:00
    @miaotaizi 你不是转 js 了嘛
    tabris17
        11
    tabris17  
       2016-07-01 10:13:29 +08:00
    用生成器模拟协程总觉得怪怪的
    500miles
        12
    500miles  
       2016-07-01 10:15:46 +08:00
    @tabris17 生成器 只是协程实现的一种功能.. 只能说用协程实现生成器
    yanyandenuonuo
        13
    yanyandenuonuo  
       2016-07-01 10:39:08 +08:00
    点赞 但是在官方看到过这样的栗子==
    darluc
        14
    darluc  
    OP
       2016-07-01 10:41:11 +08:00
    @yanyandenuonuo 本来就是翻译的人家( PHP 开发人员)的文章,看来我得把相关信息放进来。
    tabris17
        15
    tabris17  
       2016-07-01 10:45:18 +08:00   ❤️ 1
    @500miles PHP 里一直将这个称作生成器, PHP5.X 的生成器并不能很好地模拟协程,直到 PHP7 实现了 yield from 后,生成器才能真正地模拟协程
    nwmlwb
        16
    nwmlwb  
       2016-07-01 11:52:23 +08:00
    @imcxy mo'ming 莫名笑了
    broadliyn
        17
    broadliyn  
       2016-07-01 12:12:44 +08:00
    真优美。
    iyaozhen
        18
    iyaozhen  
       2016-07-01 12:45:59 +08:00 via Android
    还是有一定应用范围的,目前主要是易用性的问题。腾讯和百度都在搞相关的框架。
    lovepython
        19
    lovepython  
       2016-07-01 15:13:51 +08:00
    我去,看着跟 python 好像
    用法简直不能在像了
    darluc
        20
    darluc  
    OP
       2016-07-01 15:15:10 +08:00
    @tabris17 yield from 似乎解决了文中实现的 stacked coroutine 问题
    Actrace
        21
    Actrace  
       2016-07-01 15:16:58 +08:00
    PHP 的资料,目前还是比较少,特别是在多任务多线程这块。
    Balthild
        22
    Balthild  
       2016-07-01 22:26:45 +08:00
    我突然觉得自己看不懂 PHP 了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1031 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 20:10 · PVG 04:10 · LAX 13:10 · JFK 16:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.