本篇文章要说的时boost.asio示例代码example\http\server4中的coroutine的分析及其背后的一些历史线索。鉴于本人技术实力有限,另外只是对coroutine有兴趣,又是刚刚接触,如果有不正确的地方,还请多多指正。
coroutine的介绍和实现请见这里。coroutine在处理异步的同步问题在某些情况下非常方便,会让代码的可读性提高很多,而且也会提高异步程序的开发速度。一些动态语言就只提供了coroutine作为并行机制,比如Lua。
coroutine的实现方式有很多,比如基于状态机,生成器什么的。根据特性又分为非对称和对称coroutine,根据存储方式的不同又分为stackless和stackful。
在boost.asio中的example/http/server4中实现了一个基于状态机的coroutine的类。代码点这里
在代码中很神奇的两个关键字是reenter和yield。在一般的coroutine的实现中,yield会交出执行权,返回到调用该coroutine的函数,当再次进入这个coroutine时,会继续从上次yield之后的语句开始执行。这就是coroutine所谓的多个入口点和多个返回出口。
我把我写的example展开,发现了一些奥秘。展开后的代码见这里
仔细观察代码,实现yield的关键是那些case,大家在看的时候,把那些case -1, case 1的地方暂且放过,关注case 0还有case 999。case 0表示一段代码的起点,case 999表示yield的标记,标记这是某个yield。当执行某个yield时,会把当前yield的标记(这里是999)赋值给基类的成员变量_coro_value, 然后执行yield同行的代码。之后会退出reenter整个作用域,也就结束了整个函数。
当写很多的yield时,为了保证每个指令都有不同的标记,需要自动生成该标记。在asio.coroutine中是利用了__COUTER__宏,这个会由编译器产生不重复的标记。
既然我们知道了boost.asio.coroutine的精髓,那么我们自己实现一个。代码比较简单,状态变量使用的是static, 虽然没有考虑线程安全,但是足够使用,大家也知道,协程本身是一种线性的东西,所以也不用担心线程安全。代码见这里
看过上面的代码,大家肯定很好奇,asio实现的这种coroutine是基于了什么原理才能做到这样?
在大牛Simon Tatham(putty的作者)的一篇文章《Coroutine in C》. 中展示了如何把一个复杂的decompressor简化的过程。最终简化的代码是参考了Duff’s Device。
关于Duff’s Device,请点这里.更详细的解释见这里和这里。
Duff’s Device的实现机制:
Based on an algorithm used widely by programmers coding in assembly for minimizing the number of tests and branches during a copy, Duff’s device appears out of place when implemented in C. The device is valid C by virtue of two attributes in C:
Relaxed specification of the switch statement in the language’s definition. At the time of the device’s invention this was the first edition of The C Programming Language which requires only that the controlled statement of the switch be a syntactically valid (compound) statement within which case labels can appear prefixing any sub-statement. In conjunction with the fact that, in the absence of a break statement, the flow of control will fall through from a statement controlled by one case label to that controlled by the next, this means that the code specifies a succession of count copies from sequential source addresses to the memory-mapped output port.
The ability to jump into the middle of a loop in C.
boost.asio.coroutine的使用非常简单,在自己代码中包含coroutine.hpp和yield.hpp,创建一个类,派生自coroutine类。然后可以根据自己的业务要求写相应的coroutine了。比如开源项目avbot中的代码,可以看这里和这里。当然,也可以把coroutine类作为自己的成员变量组合到自己的类中。
reenter宏
在boost.asio.coroutine中,reenter实际上一个宏。该宏的作用是定义一个coroutine block.它的输入参数是coroutine类,这也就是为什么使用继承和组合都可以的原因。类似的代码如下:
reenter(this) { // coroutine body….}
或者reenter (coroutine_) { // coroutine body…..}
yield宏
在boost.asio.coroutine中,yield也是用宏实现的。虽然如此,但是它能够实现yield的大部分用法,比如yield taskpool.post(); 比如yield return 123; 比如yield;等等
asio的作者对自己实现coroutine做了详细的说明,请点这里。还有asio的作者写的这篇指南。下面是我写的一个example.其中task_pool_是一个线程池,可以投递任意函数执行,类似于asio.service。代码见这里
联系客服