协程到底是用来解决什么问题的?

协程到底是用来解决什么问题的?

Chris Yue No Comment
Posts

在文章开始之前,我先说说一些感受。我们好多人都觉得中国式教育,就是『好心』把知识强灌给你,但这些知识到底有什么价值,基本闭口不聊,导致大家在学习的过程中,不断问自己,『我到底在学什么?我为什么要学这个?我在哪里?』,学得是一脸蒙逼,体验非常不好。我现在都还没明白微积分和矩阵的价值是什么。这个时候可能就有人会站出来说,我知道微积分和矩阵是干什么用的巴拉巴拉…… 要我说呢,『明白』和『知道』那是两种状态,你如果明白微积分是干嘛用的,理解它的价值,那咋从来没见你在后来的工作和学习中用过呢(注:我描述的只是大部分情况,我没有资格评价真正的大牛们)?后来我编程了,免不了在网上查资料查文档,但我发现网上许多教程,其实跟大家所鄙夷的『中国式教育』也没啥不一样,直接上概念和用法。我感觉『中国式教育』体现的是授业者的懒惰:反正把我知道的说出来,其他的我不管,听不懂?给你扣个『蠢』的帽子就解决了(当然也不排除可能自己也不知道)。这当然不好,所以我创建这个《到底系列》,除了给大家通过客观真实而且细节丰富的描述来对某些网上大部分都说不清楚的知识点打破沙锅问到底,还有一个使命就是,把某些知识的价值说清楚,这两个使命都是为了体现我对『什么是良好的学习体验』的一种理解。

话不多说,开始今天的主题。今天我们聊的这个东西,可能大家都有所耳闻,可能也有一些人知道它怎么用,但就从我对周遭的感受来说,我感觉大家并不是很清楚协程的价值是什么。当然这么说其实有点过分,毕竟我也因工作原因,接触的 PHPer 比较多,肯定不能代表所有程序员的情况,其他语言的程序员怎么样,我也只能说,不清楚。不过就 PHPer 而言,这个结论还是相对客观的吧?不信的话,可以自问一下,你最近一次用协程解决实际问题是什么时候?你知道 PHP 从 5.5 版本就开始支持协程了吗?

如果上面的问题并不会让你觉得感到一丢丢惭愧,我认为你可以不用往下看了,而且我觉得你挺棒的,我得向你学习,我也是前不久才领悟到协程的作用。但如果你问答不好上面的问题,也没关系,本篇就是我的一个分享。不过先说一下,本篇并不会介绍协程的用法,网上太多了,很轻松就能搜索到。

为了不老是把『协程』打成另外一个国内知名以『杀熟』为特点的互联网公司的名字,后面我都写 coroutine 了。

Coroutine 到底是解决了什么问题?这个话题说起来我估计两三句话就讲清楚了,为了让文章看起来丰满一点(其实是为了更加利于搜索引擎的收录,咳咳……),我先说说可能大家对 coroutine 的一些误解。

在我印象当中,几乎所有跟 coroutine 有关的文章里,都会说它有很高的执行效率,并且可能都会提到多进程、或者多线程,感觉好像它跟多进程多线程是一种类似的作用似的,其实不是,coroutine 并不会同时执行多个任务(这里你可以把任务就直接理解成函数),你不能靠它来像多进程一样缩短运行时间。它的确可以用来做多个任务的切换,只不过如果 A 任务没执行完,就切换到 B,此时 A 的执行实际就停止了,所以 A,B 任务执行的总时间并没有减少,而且事实上,用了协程之后,A 切换 B 再切换回 A 的总执行时间,可能比 A 任务执行完再执行 B,理论上花的时间还多,毕竟切换任务本身肯定是要花时间的,只不过不用担心,可以完全忽略不计。总结一下:coroutine 不能缩短任务执行的时间,只能做到『看起来好像多个任务在同时执行』。

那既然 coroutine 不能减少任务执行的总时间,那为什么还要用它?这里我就要说出我对coroutine 的看法了:coroutine 的出现有很大一部分原因,其实是跟代码设计有关的,coroutine 有助于写出更加优雅的代码,这个观点我目前在网上还没见过。另外,上面 AB 任务的切换的例子太简单,完全没有用 coroutine 的动力是吧?如果我把 A 换成一个无限循环运行,只要一开始就根本停不下来的任务,这个时候你是不是就有动力去思考,怎么可以趁 A 不注意的时候,偷偷运行一下 B 呢?下面我将给出一个比较实际的例子。

面向 socket 编程大家应该或多或少有所了解,比如说我现在要做一个聊天室服务器,那我这服务器的轮询里的代码,肯定要做这么两件事情:

  1. 本轮查询一下有没有新的客户端链接
  2. 接着轮询一下有没有新的客户端消息进来

上面的两件事情可以用下面的伪代码表示

从代码的设计来看,上面的代码是很糟糕的,一是为什么要把轮询客户端链接的代码和轮询客户端消息的代码写在一块儿呢?他们相互之间本来也没有什么关系。是不是可以稍微封装一下,把他们俩的逻辑分开呢?二是,我可能还想对这两件事情的执行有一些控制,我希望让用户在链接服务器的时候能得到更快的响应,而相对的,处理新消息的时候可以稍微慢一些,反正用户也不知道是服务器慢还是对方回得慢,是吧…… 大家可以想想如果不用 coroutine,上面的需求需要怎么实现,想好之后(虽然我估计你们也不会想……),再看看下面用 coroutine 怎么做。

轮询是否有新链接的代码

轮询是否有新消息的代码

一个优先处理新链接的 run 函数

看到这里,不知道大家是否对什么时候用 coroutine 有了一些感觉呢?如果已经有了,继续往下看,咱们再聊一些有意思的事情。

彩蛋:

PHP 刚推出 yield 关键词的时候,大家很快就能理解的用法,应该就是当 iterator 用了。这里跟可能不熟悉 PHP 的 yield 关键词的小伙伴解释一下哈,如果一个函数或者方法,函数体内出现了 yield 关键词,那么这个函数的返回值类型,就一定是 Generator,而 Generator 是实现迭代器(iterator)接口的,例子也很经典,我之前的文章也曾引用过:

我现在的问题是,既然 Generator 就是迭代器,Generate 又可以当 coroutine 用,那是不是 iterator 本来就可以当 coroutine 用?这里说一下我的看法:iterator 可以当半个 coroutine 用。也就是说,其实 PHP 在 5.5 版本之前,就可以通过 iterator 实现一半的 coroutine 功能了,比如说上面等待 socket 新链接的例子,也可以这么做:

然后 run 函数只用改一行代码,也一样运行

只不过使用 yield,代码少好多,不用新建一个类,省事儿;不止是这样,用 yield 其实更加的自然,代码更容易写(要不然我为啥只拿 connectionLoop 举例子,而不提 messageLoop 呢)。

然鹅……,为什么又说 iterator 只能实现半个 coroutine 的功能呢?这个我们可以从 Generator 提供的比 iterator 多的接口可以看出来。这里开始划重点了,其中的 sendthrow 方法,给了可以给 Generator 传消息的可能性!这是跟普通函数只能接受若干值然后返回一个值的最大不同。这其实给我们很大的想像空间,意味这我们不仅可以控制多个任务的切换,而且还可以通过给代表这些任务的 Generator 传递消息,控制任务内部的执行的细节。如果大家明白了我说的意思,再回过头来看看网上跟 coroutine 相关的教程,不知道会不会更容易接受一些。

Coroutine 相关的话题今天就说这么多了,如果大家觉得有帮助,就请扫一下下方的赞赏码鼓励一下作者吧!

协程到底是用来解决什么问题的? by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

文章不错,我要帮站长分担建站费!
天使投赏人

发表评论

eighty six − = eighty four