setcontext族函数是用于实现上下文控制的一组函数,它们可以实现setjmp和longjmp无法实现的高级控制,可以在用户代码级别实现模拟的线程,例如GUN的Pth项目,就是利用这setcontext族函数模拟一个与Pthread兼容的用户代码级别线程库。
最近在研究C/C++中像Erlang那样进行并行计算是否可行,所以研究到了协程。
协程相较于线程,具有消耗小,可控等特点。一个同时在线几千上万人的网络应用,不可能把每个客户端连接都用一个系统线程来对应,但是在Erlang中却可以用Erlang进程来对应每个连接。
将客户端与进程(这里统称Erlang进程和协程)以1:1对应的方式进行设计,这对化简服务器程序设计是非常有用的。
C语言中著名的协程项目libtask正是利用setcontext族函数来实现协程,当目标系统不支持setcontext族函数时才用汇编模拟。
libtask的运作方式与Go语言对并行操作的模拟方式更接近,几乎是一模一样的设计,因为libtask和Go语言都是跟Plan 9有颜渊的,据我了解Go和libtask提供的Channel作为协程间通讯的方式都源自Plan 9。
但是这种方式跟Erlang的Actor模型是不一样的,到底哪种方式更适合C/C++还有待深入研究。
今天做了一个setcontext族函数的实验,用于学习其中几个函数的用法。
实验的内容是分别创建ping和pong两个"协程",一个输出ping,一个输入pong,"调度器"每次上下文切换输出switch,这里协程和调度器打引号是因为直接调用的makecontext和swapcontext函数,看起来一点都不像协程和调度器,但实际上这个实验可以看作是一个最简单的协程调度实验。
完成调度实验后,我加入了上下文切换耗时的计算,分别在我的电脑上(VMWare中的Ubuntu 11.10)和服务器上(Dell R410)进行了实验,10万次上下文切换的耗时在两个实验环境都是0.07秒,我想这个性能足以在一个进程中模拟成千上万的协程了。
最终代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ucontext.h>
ucontext_t main_ctx, ping_ctx, pong_ctx;
char ping_stack[1024];
char pong_stack[1024];
void ping(void)
{
for (;;) {
//printf("ping\n");
swapcontext(&ping_ctx, &main_ctx);
}
}
void pong(void)
{
for (;;) {
//printf("pong\n");
swapcontext(&pong_ctx, &main_ctx);
}
}
int main(int argc, char **argv)
{
int i, ctx_id = -1;
clock_t t1, t2, ref;
getcontext(&ping_ctx);
ping_ctx.uc_stack.ss_sp = ping_stack;
ping_ctx.uc_stack.ss_size = sizeof(ping_stack);
ping_ctx.uc_link = &main_ctx;
makecontext(&ping_ctx, ping, 0);
getcontext(&pong_ctx);
pong_ctx.uc_stack.ss_sp = pong_stack;
pong_ctx.uc_stack.ss_size = sizeof(pong_stack);
pong_ctx.uc_link = &main_ctx;
makecontext(&pong_ctx, pong, 0);
t1 = clock();
for (i = 0; i < 100000; i ++) {
}
t2 = clock();
ref = t2 - t1;
t1 = clock();
for (i = 0; i < 100000; i ++) {
//printf("switch, ");
ctx_id = (ctx_id + 1) % 2;
if (ctx_id == 0)
swapcontext(&main_ctx, &ping_ctx);
else
swapcontext(&main_ctx, &pong_ctx);
}
t2 = clock();
printf("%lf seconds\n", (double)(t2 - t1 - ref) / CLOCKS_PER_SEC);
}
实验中我遇到两个问题,一个是必须对新的上下文调用setcontext,另一个是新上下文的堆栈必须分配,否则会出现段错误。
原标签已从 2012-01-11 存档快照 恢复。本文按原站快照恢复排版,并接回已核实的本地图片。查看恢复说明