基于ucontext的协程
ucontext是glibc下的组件,用来管理程序执行的上下文,重点是四个函数:
1 |
|
在使用ucontext封装c++风格的轻量级协程的过程中,主要是干下面几件事:
- 对调度器、协程的资源初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class Coroutine{
public:
Coroutine(coroutineFunc func, void* args, CoScheduler* sche);
~Coroutine();
void saveStack(char* top);
void dofunc();
int getStatus(){
return status_;
}
void setId(int id){
id_ = id;
}
public:
coroutineFunc func_;
void* args_;
CoScheduler* sehcduler_;
ptrdiff_t capacity_;
ptrdiff_t curSize_;
ucontext_t ctx_;
CoStatus status_;
char* stack_;
int id_;
};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
28class CoScheduler{
public:
CoScheduler();
~CoScheduler();
int createCoroutine(coroutineFunc func, void* args);
void deleteCoroutine(int id);
void resume(int id);
void yield();
int currentCo(){
return runningCo_;
}
int getStatus(int id){
assert(id>=0 && id < capacity_);
if (coroutines_[id] == nullptr) {
return COURTINUE_DEAD;
}
return coroutines_[id] -> status_;
}
public:
CoScheduler(const CoScheduler&) = delete;
CoScheduler& operator=(const CoScheduler&) = delete;
char stack_[STACK_SIZE];
ucontext_t mainCtx_;
int numCoroutines_;
int capacity_;
int runningCo_;
std::vector<Coroutine*> coroutines_;
}; - 对调度器调度方式实现。
这里主要有对称和非对称之别,主要区别:
- 非对称:类似于函数调用,某个协程切换到另一个协程,最终是要返回到本协程调用处的。在实现基于ucontext的协程的过程中,采用的是非对称方式,主要是因为非对称的方式比较好控制。
- 对称则不存在这种关系,程序执行权在协程之间任意切换。例如go的协程。
- 协程堆栈的封装
在有栈协程里,对协程堆栈的实现也分为两种,一种是共享堆栈,一种是独立栈。
- 独立栈:每个协程都有自己的堆栈数据,每次执行之后保存,优点是切换速度快,缺点是需要分配足够大的内存,否则运行过程会导致栈溢出。
- 共享栈:在主协程分配足够大的栈空间,每次执行将协程堆栈拷贝到此空间,缺点是拷贝耗时,优点是省空间。
除此之外,由于ucontext有浮点数上下文和sigmask(信号屏蔽掩码)的保存,因此切换的效率并不高,在一些工业级的应用场景下,常常自定义汇编代码进行上下文切换,如libco,接下来我会参考libco来使用x86汇编实现一个小型协程库。