C++协程及其原理

协程的几种实现方式及原理

协程又可以称为用户线程,微线程,可以将其理解为单个进程或线程中的多个用户态线程,这些微线程在用户态进程控制和调度.协程的实现方式有很多种,包括

  1. 使用glibc中的ucontext库实现
  2. 利用汇编代码切换上下文
  3. 利用C语言语法中的switch-case的奇淫技巧实现(protothreads)
  4. 利用C语言的setjmp和longjmp实现

实际上,无论是上述那种方式实现协程,其原理是相同的,都是通过保存和恢复寄存器的状态,来进行各协程上下文的保存和切换。

协程较于函数和线程的优点

  • 相比于函数:协程避免了传统的函数调用栈,几乎可以无限地递归
  • 相比与线程:协程没有内核态的上下文切换,近乎可以无限并发。协程在用户态进程显式的调度,可以把异步操作转换为同步操作,也意味着不需要加锁,避免了加锁过程中不必要的开销。

进程,线程以及协程的设计都是为了并发任务可以更好的利用CPU资源,他们之间最大的区别在于CPU资源的使用上:

  • 进程和线程的任务调度是由内核控制的,是抢占式的;
  • 协程的任务调度是在用户态完成,需要代码里显式地将CPU交给其他协程,是协作式的

由于我们可以在用户态调度协程任务,所以我们可以把一组相互依赖的任务设计为协程。这样,当一个协程任务完成之后,可以手动的进行任务切换,把当前任务挂起(yield),切换到另一个协程区工作.由于我们可以控制程序主动让出资源,很多情况下将不需要对资源进行加锁。

Comment and share

ucontext族函数详解

the ucontext_t type is a structure type suitable for holding the context for the user thread of execution. A thread’s context include stack,saved registersm a list of block signals

上述为ncontext_t结构体的定义,ucontext机制是GNU C库提供的一组创建,保存,切换用户态执行上下文的API,从上面的描述可以看出ucontext_t结构体使得用户在程序中保存当前的上下文成为可能。我们也可以利用此实现用户级线程,即协程。

ucontext_t以及ucontext族函数

ucontext_t结构体

ucontext_t结构体定义,一个ucontext_t至少包括以下四个成员,可能依据不同系统包括其他不同的成员。

1
2
3
4
5
6
7
8
#include <ucontext.h>
typedef struct ucontext_t {
struct ucontext_t* uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
...
};

类成员解释:

  • uc_link:为当前context执行结束之后要执行的下一个context,若uc_link为空,执行完当前context之后退出程序。
  • uc_sigmask : 执行当前上下文过程中需要屏蔽的信号列表,即信号掩码
  • uc_stack : 为当前context运行的栈信息。
  • uc_mcontext : 保存具体的程序执行上下文,如PC值,堆栈指针以及寄存器值等信息。它的实现依赖于底层,是平台硬件相关的。此实现不透明。

ucontext族函数

ucontext族函数主要包括以下四个:

1
2
3
4
5
#include <ucontext.h>
void makecontext(ucontext_t* ucp, void (*func)(), int argc, ...);
int swapcontext(ucontext_t* olducp, ucontext_t* newucp);
int getcontext(ucontext_t* ucp);
int setcontext(const ucontext_t* ucp);

  • makecontext:初始化一个ucontext_t,func参数指明了该context的入口函数,argc为入口参数的个数,每个参数的类型必须是int类型。另外在makecontext之前,一般需要显示的初始化栈信息以及信号掩码集同时也需要初始化uc_link,以便程序退出上下文后继续执行。
  • swapcontext:原子操作,该函数的工作是保存当前上下文并将上下文切换到新的上下文运行。
  • getcontext:将当前的执行上下文保存在ucp中,以便后续恢复上下文
  • setcontext : 将当前程序切换到新的context,在执行正确的情况下该函数直接切换到新的执行状态,不会返回。

注意:setcontext执行成功不返回,getcontext执行成功返回0,若执行失败都返回-1。若uc_link为NULL,执行完新的上下文之后程序结束。

简单实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <ucontext.h>
#include <iostream>
#include <unistd.h>
void newContextFun() {
std::cout << "this is the new context" << std::endl;
}
int main() {
char stack[10*1204];
//get current context
ucontext_t curContext;
getcontext(&curContext);
//modify the current context
ucontext_t newContext = curContext;
newContext.uc_stack.ss_sp = stack;
newContext.uc_stack.ss_size = sizeof(stack);
newContext.uc_stack.ss_flags = 0;
newContext.uc_link = &curContext;
//register the new context
makecontext(&newContext, (void(*)(void))newContextFun, 0);
swapcontext(&curContext, &newContext);
printf("main\n");
return 0;
}

Comment and share

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China