狂拽酷炫吊炸天:用 PHP 协程实现多任务协作 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
darluc
V2EX    PHP

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

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

    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 的协程简直若爆了
    icxy
        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-0 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     Solana     1231 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 47ms UTC 17:21 PVG 01:21 LAX 09:21 JFK 12:21
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86