darluc
V2EX  ›  PHP

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

  •  
  •   darluc · Jul 1, 2016 · 8893 views
    This topic created in 3615 days ago, the information mentioned may be changed or developed.

    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'。

    继续阅读请点击此处

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