协程(coroutine),意思就是“协做的例程”(co-operative routines),最先由Melvin Conway在1963年提出并实现。跟主流程序语言中的线程不同,线程属于侵入式组件,线程实现的系统称之为抢占式多任务系统,而协程实现的多任务系统成为协做式多任务系统。线程因为缺少yield语义,因此运行过程当中不可避免须要调度,休眠挂起,上下文切换等系统开销,还须要当心使用同步机制保证多线程正常运行。而协程的运行指令系列是固定的,不须要同步机制,协程之间切换也只涉及到控制权的交换,相比较线程来讲是很是轻便的。不过同一时刻能够有多个线程运行,但却只能有一个协程运行。
实际上协程的概念比线程还要早,按照 Knuth 的说法“子例程是协程的特例”,一个子例程就是一次子函数调用,那么实际上协程就是类函数同样的程序组件,你能够在一个线程里面轻松建立数十万个协程,就像数十万次函数调用同样。只不过子例程只有一个调用入口起始点,返回以后就结束了,而协程入口既能够是起始点,又能够从上一个返回点继续执行,也就是说协程之间能够经过 yield 方式转移执行权,对称(symmetric)、平级地调用对方,而不是像例程那样上下级调用关系。固然 Knuth 的“特例”指的是协程也能够模拟例程那样实现上下级调用关系,这就叫非对称协程(asymmetric coroutines)。多线程
setjmp/longjmp 实际上是C语言标准库中的内容,它被定义在<setjmp.h>头文件中,我认识的至关部分的人包括写过不少年C/C++的都表示没听过,而且他们在了解了一些setjmp的特性和功能以后还不觉得然,说我又不会用到它;然而大家想过为何标准库中会去实现一个相对这么怪异特性的语法支持?缘由很简单,就是为了实现协程(coroutine),若是你一开始就给本身定位成协程的使用者,不关心它具体怎么实现的,甚至给本身定位成从不用协程,后面的内容你放心能够直接略过。
咱们首先来看 setjmp/longjmp 这两个函数的定义。函数
int setjmp( jmp_buf _Buf ); void longjmp( jmp_buf _Buf, int _Value);
使用注意事项:
一、setjmp与longjmp结合使用时,它们必须有严格的前后执行顺序,也即先调用setjmp函数,以后再调用longjmp函数,以恢复到先前被保存的“程序执行点”。不然,若是在setjmp调用以前,执行longjmp函数,将致使程序的执行流变的不可预测,很容易致使程序崩溃而退出
二、longjmp必须在setjmp调用以后,并且longjmp必须在setjmp的做用域以内。具体来讲,在一个函数中使用setjmp来初始化一个全局标号,而后只要该函数不曾返回,那么在其它任何地方均可以经过longjmp调用来跳转到 setjmp的下一条语句执行。实际上setjmp函数将发生调用处的局部环境保存在了一个jmp_buf的结构当中,只要主调函数中对应的内存不曾释放 (函数返回时局部内存就失效了),那么在调用longjmp的时候就能够根据已保存的jmp_buf参数恢复到setjmp的地方执行。
说白一点就是:在使用 setjmp 时,最多见的一个错误用法就是对它作封装,不该该封装在一个函数中。好比:spa
int try(breakpoint bp) { return setjmp(bp->jb); } void throw(breakpoint bp) { longjmp(bp->jb,1); }
这样写并不会引发编译错误,可是极易容易发生运行时错误,由于setjmp的栈是在try函数中,而下一次调用longjmp的时候try函数可能已经不在栈中被清除了。
来个简单的例子:线程
#include <stdio.h> #include <setjmp.h> jmp_buf buf; void second() { printf("second\n"); longjmp(buf, 1); } void first() { second(); printf("first\n"); } int coro_main() { if ( !(setjmp(buf)) ) { first(); } else { printf("main\n"); } return 0; }
输出结果:code
second
main
除此以外还有广为使用的C语言协程非标准库有 ucontext,据我所知ucontext应用更普遍一些,网上绝大多数 C 协程库也是基于 ucontext 组件实现的。有空下次再去研究研究它。。。协程