基于ucontext_t实现的非对称协程,本章对应源码:zhongluqiang/sylar-from-scratch at 4419dbda8cf40d9b9d3bb0b0b14242a4d1b8aa2c。
参考以下链接:
libaco: 一个极速的轻量级 C 非对称协程库 🚀 (10 ns/ctxsw + 一千万协程并发仅耗内存 2.8GB + Github Trending) - 燕云 - 博客园
一文彻底弄懂C++开源协程库libco——原理及应用 - 知乎
NtyCo的实现 · wangbojing/NtyCo Wiki
以上协程库最好跑一下测试用例,感受一下协程的使用。
建议初学者在开始学习协程时,不要尝试深入x86/x64结构体系和汇编语言去了解协程上下文和协程切换原理,只需要了解协程是什么,协程上下文和协程切换是怎么回事即可。另外,特别说明,NtyCo的配套视频又臭又长,不要去看。
看了上面的链接还不了解协程的可以往下看。
最简单的理解,可以将协程当成是一种看起来花里胡哨,并且使用起来也花里胡哨的函数。协程的本质就是被包装起来的函数和函数的运行状态,称为协程上下文。因为每个协程在创建时一定会指定一个入口函数,这点和线程完全一样。协程和函数的不同之处是,函数一旦被调用,只能从头开始执行,直到函数执行结束退出,而协程则可以执行到一半就退出(称为yield),但此时协程并未结束,它可以在后面适当的时机再重新被恢复(称为resume),在这段时间间隔内可以运行其他的协程,所以协程也被称为用户态线程。
简单来说,协程就是用户态线程。从使用上来说,协程和线程确实有许多相似的地方,比如,协程和线程都有自己的入口函数和执行栈,线程之间可以切换执行,协程之间也可以切换执行。但是,线程和协程是有本质区分的。
协程切换最重要的就是协程上下文,它代表了函数的执行状态。
非对称协程。
非对称协程,每个线程的入口函数作为主协程,其他协程为子协程,协程只能在主协程和子协程之间进行切换,不能在子协程与子协程之间切换。所对,这个协程模块最大的一点限制是,子协程不能创建并运行子协程,所有的协程都只能由主协程进行创建并调用。这个限制在引入调度器后可以通过调度器接口来规避掉,在使用调度器时,协程可以通过向调度器添加调度任务的方式来启动新的协程。
在非对称协程的实现中,每个线程永远只关心两个协程(由线程局部变量记录),一个是线程主函数的协程,另一个是子协程,在任意时间,要么主协程在前台运行、子协程在后台等待,要么子协程在前台运行、主协程在后台等待,绝对不会出现在前台运行和后台等待的协程都是子协程的情况。
协程:用户态的线程,相当于线程中的线程,更轻量级。后续配置socket hook,可以把复杂的异步调用,封装成同步操作。降低业务逻辑的编写复杂度。 目前该协程是基于ucontext_t来实现的,后续将支持采用boost.context里面的fcontext_t的方式实现。
协程原语:
`resume`:恢复,使协程进入执行状态
`yield`: 让出,协程让出执行权
yield和resume是同步的,也就是,一个协程的resume必然对应另一个协程的yield,反之亦然,并且,一条线程同一时间只能有一个协程是执行状态。
协程关键:
1. 协程虽然被称为轻量级线程,但在同一个线程内,协程并不能并发执行,而是只能按顺序执行。其实这点也好理解,毕竟协程其实就是以一种花里胡哨的方式调用子函数,不管实现得如何巧妙,也不可能在单线程里做到同时运行两个子函数,否则还要多线程有何用?
2. 因为单线程下协程并不是并发执行,而是顺序执行的,所以不要在协程里使用线程级别的锁来做同步,比如pthread_mutex_t。如果一个协程在持有锁之后让出执行,那同线程的其他任何协程只要一旦尝试再次持有这个锁,整个线程就锁死了,这和单线程环境下,连续两次持有同一个锁导致的死锁原理完全一样。