Skip to content

协程

生命永存

分有栈和无栈,用这个东西主要是引入异步的机制,但是需要注意的一点是分配出来的内存都是在堆上的,而不是在栈上的,因为栈可能随着调用者栈的解退而解退,而协程要独立于这个而存在,因此协程的空间分配在堆上。

有栈协程,他的机制更像用户态线程,在调度协程的时候,也和正常的线程上下文切换一样要保存寄存器。有栈协程的实现是通过库实现的,协程库中通过内联汇编封装了上下文切换的过程。这种方式的实现是侵入比较小的,只要把原先的函数作为参数传给协程库做调用就行了,同时如果你想用第三方库的话且第三方库的有回调的话,使用有栈是合适的,因为他保存pc,理论上可以返回到最开始的调用者。go 的协程是有栈的,并且语言运行时内置了调度器,感觉 M:N 的设计,感觉和 seastar 的设计是很像的。

cpp 的协程是无栈实现,并且引入了 co_await co_yeild co_return 等关键字,只要函数里面调了,会被认为是一个协程。无栈协程是编译器支持的,空间在编译的时候算好,但是分配还是分配在堆上,编译器将协程转成成一个状态机,整个状态机都依靠内置的状态进行推进,因此他没有记录线程的上下文,该记的上下文都被存到结构体中对应的状态里,回到协程只要 jmp 到结构体中对应函数的入口就行了。这是一种 future/promise 的编程模式,future 代表协程返回的结果,promise 记录协程内部的状态,好比记录什么东西做完了没有。

异步编程

反正异步编程的核心就是记录一笔,主线程继续做自己的,然后到点了检查。这个过程是必须的,不然没有人告诉你完成了没有。seastar 的主线程在一开始的时候会有一堆 poller 去做这件事。异步编程还需要避免的是,在协程里面做同步系统调用,因为系统调用是依附于线程的,你一做,线程直接被让掉了,如果要做同步的话,要从线程池里抽个线程出来做。如果是异步的话,用 linux 的 epoll 和 iouring 相关的机制就行了。对于无栈协程,promise 的状态需要有一个地方存着,起码能够找到,这样等查到哪个 promise 做完的时候,还能找出来。