上一篇说了coroutine的本质是什么,就是resumable function,那么一个函数有了suspend和resume功能之后,会打开什么样的新世界大门呢?随便举几个例子。
- 函数每次被唤醒,就丢出一个值,然后暂停——这是generator
- 函数启动一个IO操作,注册IO操作完成时唤醒自己,然后暂停——这是async-await
- 函数开启一个管道,暂停,另一个函数往管道里写一点东西,然后唤醒它——这是channel
- 函数检查某个值是不是期望的,如果不是,就暂停——这是exception
其实,2和3的机制是很相似的,只不过2里面唤醒coroutine的是操作系统的callback,3是你自己的另一个线程。4里面说的异常,其实就是这样的,因为异常和coroutine再往下追溯,他们的理论基础都是CPS(Continuation Passing Style),也就是把当前操作的后续操作作为闭包传给当前操作,而当前操作可以选择执行哪一个(或是否执行)后续操作。虽然coroutine和exception都用到了CPS,但大部分的coroutine都额外支持了exception,耦合在了一起,因为他们没有直接提供操作continuation的东西。
扯远了,可以看到了,coroutine解锁了很多写代码的新姿势。
有了generator,你可以生成一个惰性求值的列表,对他进行变换,而这些所有的操作都是惰性执行的,这就是C#里面的LINQ,Python和Javascirpt里的生成器,Java8的Stream API,C++的range-ts也可以接入coroutine,而避免使用复杂的迭代器封装状态。
有了async-await,你可以把异步代码写成像同步代码那样,而代码在await的边界处是自动暂停和继续的,这无疑降低了程序员手动写状态机维护状态的难度,也避免了一连串.then造成回调地狱的问题。channel和异步操作的async-await很相似,两条线程如今可以主动的暂停自己和唤醒对方,通过一个普通的原子变量来传递信息,而不需要用厚重的管道或者多线程同步机制来等待和唤醒对方。
关于coroutine模拟exception,这倒是没有太大的必要,不过,考虑到coroutine的本质是CPS,那么就可以用coroutine来模拟rust的自动向上传播错误码,haskell的maybe monad等等,这些东西是对应语言的错误处理机制,就像C++的异常一样。
脑洞,反过来,异常可以模拟coroutine吗?不行,异常有点像暂停之后再也不会唤醒的coroutine。