介绍
  在前几天接触到了协程的概念,觉得很有趣。因为我可以使用一个线程来实现一个类似多线程的程序,如果使用协程来替代线程,可以省去很多原子操作和内存栅栏的麻烦,大大减少与线程同步相关的系统调用。因为我只有一个线程,而且协程之间的切换是可以由函数自己决定的。
  我有见过几种协程的实现,因为没有 C/C++ 的原生支持,所以多数的库使用了汇编代码,还有些库利用了 C 语言的 setjmp 和 longjmp 但是要求函数里面使用 static local 的变量来保存协程内部的数据。我讨厌写汇编和使用 static local 变量,所以想出了一种稍微优雅一点又有点奇技淫巧的实现方法。 这篇文章将向你展示这种方法基本原理和实现。
  基本原理
  用 C/C++ 实现的大困难是创建,保存和恢复程序的上下文。因为这涉及到了程序栈的管理,以及 CPU 寄存器的访问,但是这两项内容在 C/C++ 标准里面都没有严格的定义,所以我们是不可能有一个完全跨平台的 C/C++ 实现的。但是利用操作系统提供的 API,我们仍然可以避免使用汇编代码,接下来会向你展示使用 POSIX 的 pthread 实现的一种简单的协程框架。什么!??Pthread?那你的程序岂不是多线程了?那还叫协程吗!没错,确实是多线程的,不过仅仅是在协程被创建之前的短暂瞬间。
  要创建子程序的上下文,我们可以调用 pthread_create 函数来创建一个真正的线程,这样操作系统会帮我们创建上下文(这里包括初始化 CPU 寄存器和程序栈)。然后在线程启动时,使用 C 语言的 setjmp 把这些寄存器备份到外部的 buffer 里面。创建完后,这个线程便失去了它的存在价值,所以可以果断干掉它了。不过还需要注意一点,是在创建线程之前,需要调用 pthread_attr_setstack 函数来显式地声明使用的程序栈,这样线程退出的时候,系统不会自动销毁这个程序栈。至于上下文的恢复,显然是使用 longjmp 函数了。
  创建上下文
  下面是 RoutineInfo 的定义。为了简单起见,所有错误处理代码都被省略了,原版本的代码在 coroutine.cpp 文件中,省略版的代码在 coroutine_demonstration.cpp 文件中。
typedef void * (*RoutineHandler)(void*);
struct RoutineInfo{
void * param;
RoutineHandler handler;
void * ret;
bool stopped;
jmp_buf buf;
void *stackbase;
size_t stacksize;
pthread_attr_t attr;
// size: the stack size
RoutineInfo(size_t size){
param = NULL;
handler = NULL;
ret = NULL;
stopped = false;
stackbase = malloc(size);
stacksize = size;
pthread_attr_init(&attr);
if(stacksize)
pthread_attr_setstack(&attr,stackbase,stacksize);
}
~RoutineInfo(){
pthread_attr_destroy(&attr);
free(stackbase);
}
};