协程与回调

来源:互联网 发布:纯四个数字域名价格 编辑:程序博客网 时间:2024/05/21 08:37
异步回调方式的优点很多,大家都应该知道的,我就不多说了,只说说它的缺点,一言以蔽之:
反人类。

正常人的思维基本是线性的,当然可以做一些局部的分支判断,但你的思维只能跟着其中的一个分支连贯的走下去。
异步回调最大的问题就是破坏掉了人类这种线性的思维模式,你必须把一个逻辑上线性的过程切分成若干个片段,每个片段的起点和终点就是异步事件的完成和开始。固然经过一些训练你可以适应这种思维模式,但你还是要付出额外的心智负担。
与人类的思维模式相对应,大多数流行的编程语言都是命令式的,程序本身呈现出一个大致的线性结构。异步回调在破坏点思维连贯性的同时也破坏掉了程序的连贯性,让你在阅读程序的时候花费更多的精力。
这些因素对于一个软件项目来说都是额外的维护成本,所以大多数公司并不是很青睐node.js或者RxJava之类的异步回调框架,尽管这些框架能提升程序的并发能力。
协程本来是个很古老的东西,很多年没多少人太在意过它,但针对异步回调反人类这件事,协程才又被人从故纸堆里翻出来了,因为它刚好可以把异步回调的碎片化程序重新组织成一个线性过程,同时还可以最大限度的保留异步回调结构带来的并发性能。
当然一些FP出身的开发人员对协程不太感兴趣,因为FP程序本来的结构就和异步回调风格接近,他们已经习惯了,但对于传统的命令式语言出身的开发人员来说,协程可以极大的降低理解难度和维护成本。

不过话说回来,在本来已经很复杂的异步回调框架上再套一层一样很复杂的协程框架并不一定真的能简化问题,反而会带来一些兼容性问题,所以到现在为止还处于一个雷声大雨点小的状态。


首先去刷汇编的寄存器和远跳指令,搞清楚协程切换不过就是保存寄存器+跳转到另一个函数的半中间。在此之上可以构建个调度器,也可以不构建。刷TAOCP卷一的coroutine那一章也行。不弄懂协程的运作机制下盘不稳学起来也不踏实不是?

明白之后可以考虑同步接口,异步接口和协程的关系了。

假设你有10个文件,你想把10个文件拼接起来模拟成一个大文件。但是你内存又没那么大,那只好读一点让用户消耗一点了。所以你得写个filecat这样的adaptor。

如果所有接口都用同步,用户是爽了,仿佛直接在读一个文件;你实现得很蛋疼,每当用户问你要数据的时候,你得先检查我现在读到第几个文件了,读完了没有,后面还有没有别的文件了,要维护一坨状态。

如果所有接口都用异步,你是爽了,直接把用户的回调挨个传到不同的文件读写调用里去;用户则蛋疼了。要是用户想读三行,停一停去做些别的(比如处理下这三行),是很麻烦的,因为你这个adaptor往用户端塞数据塞得根本停不下来,用户有什么想法都得往回调函数里塞。“我现在读到第几行了?如果小于三行,就先送到处理头三行的函数里去;如果等于三行,就要拿一下处理好的三行并送去不知道什么地方,如果大于三行,再干点别的。。。”所以用户得手动维护这些状态。

你会发现无论是同步还是异步,都有这种“手动维护状态”的问题。要想让adaptor和用户都开心,解法自然是协程。我现在就用代码来表示状态,执行一行就是状态的转移。但是两头的状态要交替变化,这边做三行,轮到那边做了;那边做完了,又回到这边来,这跳来跳去的,就是协程了。

----------------------------------- 萌萌哒分割线 -----------------------------------

讲点历史
吧。

早期的Unix时代崇尚同步编程,当时设计的时候也没多想,管他什么性能不性能,先做出来再说。所以最开始人是开多进程(process)再用管子(pipe)连接起来的;多直观啊,我把东西推进管子里,你走你那头接一下。

不知道哪天人注意到开个千儿八百个进程机器就卡死了,这怎么行,所以我们只搞一个进程,里面有一坨打开的网络连接和文件,用select这个系统调用对io事件同时进行监听,谁先来我就处理谁。然而发现性能也不好,没什么卵用。

人们在这两条路上都想办法提升性能。进程这边人们又搞了多线程,结果还是不够。多线程还是吃内核资源,抗不住。

Linux忍不住了,搞出个epoll(不过我不知道和bsd的kqueue哪个在前)系统调用,就是红黑树改良版的select。这下子人开心了,真他妈快啊,开千儿八百个连接小意思,同时进行事件监听,判断连接的的id和事件的类型,再打(dispatch)到不同的回调函数里。

不知是不是市场原因,正巧这时候互联网开始火了,性能越好赚钱越多嘛,所以肯定来这套高效的接口,管他好不好用呢,反正俺们吃苦耐劳,外加抖M,才不怕手动维护状态。你多线程开太多最后不也是要手动维护状态。

不熟悉操作系统的开发人员们只知道有什么用什么,也没去多想怎么优化多线程。现在多线程优化出来了,叫协程,调度器有的有(比如go的用户态调度器)有的没有(比如yield),取决于怎么用。其内存开销仍然比异步回调要大(一个协程一个栈,而异步回调的话一个event loop一共就一个栈),但是现在内存也是便宜了,不算是瓶颈。人最关注的是“千万不能堵着(block)”,要千方百计让CPU转起来,这样concurrency才能上去。而到处乱开协程(因为比线程便宜啊)就能达到这个效果,开十万个协程内存也没爆炸,跑上24个就能把CPU打满。所以异步回调的这个优势已经没了。

----------------------------------- 没那么萌哒分割线 -----------------------------------

不是我说,因为异步用多了很熟悉就说方便好用,这是不太客观的说法。而协程方便是因为“用代码来表示状态,而不是维护一坨数据结构来表示状态”,则客观得多。

我注意到从事应用开发和网络服务开发的人员对于底层可能没那么熟悉,有时候不免走了弯路。kqueue/epoll这么个简单的东西,被开发者们搞出花来了,异步编程事件回调一套一套的,写书的也有,写库的也有(最近那个reactive也是)。自然术业有专攻,但是倘若稍微花些精力过一遍底层概念,学习/发明上层东西的时候会快很多,也少些痛苦。

至于趋势什么的,也就赶个时髦吧,这些枝叶伎俩学学也要不了几个小时。如果是担心要花很多精力学习某个“技术”,又怕学了没用,故有“是不是趋势啊”这一问,那多半是根基不牢,打打基础就行了。

我是写C++的,我发现我唯一用callback/closure/lambda的地方是我有个函数,但是我想把这个函数扣个洞,让用户来填这个洞。用得还真不算多。
转载自:http://www.zhihu.com/question/32218874
0 0