协程,作为go语言的亮点,可以实现用户在用户层面根据业务实际情况来实现进程切换。但是由于C语言才是运行速度最快的语言,并且在很多时候,比如mips架构等,均只支持C语言,因此C语言实现协程功能对于开发人员来说是完全必要的功能。
下面小沃就来教教大家在C语言环境下如何实现协程功能。
这里主要用到了3个函数:getcontext,makecontext,swapcontext,这三个函数的功能分别是:
getcontext:初始化上下文
makecontext:创建协程与其要调用的函数
swapcontext:切换到协程去(或从协程切换回来)
在软件开发中,往往CPU运算速度远远高于IO的响应速度,因此将IO请求设置为非阻塞,然后让出CPU,最后通过epoll等特性监听IO,当IO再次响应后,切换回原来的协程是大家开发高并发应用的常用方法,下面小沃提供一个小的切换协程的程序给大家演示下如何进行协程切换。
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#define STACKSIZE 2*1024
static ucontext_t main_ctx; // 主函数的上下文
void coroutine1 (ucontext_t* ctx) {
printf("hello, I am coroutine1\n");
if (swapcontext(ctx, &main_ctx)) { // 函数执行到一半让出cpu给主进程
printf("coroutine1让出cpu失败\n");
return;
}
printf("hello, I am coroutine1 again\n");
}
void coroutine2 (ucontext_t* ctx) {
printf("hello, I am coroutine2\n");
if (swapcontext(ctx, &main_ctx)) { // 函数执行到一半让出cpu给主进程
printf("coroutine2让出cpu失败\n");
return;
}
printf("hello, I am coroutine2 again\n");
}
void coroutine3 (ucontext_t* ctx) {
printf("hello, I am coroutine3\n");
if (swapcontext(ctx, &main_ctx)) { // 函数执行到一半让出cpu给主进程
printf("coroutine3让出cpu失败\n");
return;
}
printf("hello, I am coroutine3 again\n");
}
int main () {
// 先创建3个协程的上下文
ucontext_t* coroutine_ctx1 = (ucontext_t*)malloc(sizeof(ucontext_t));
if (coroutine_ctx1 == NULL) {
printf("申请第1个协程的上下文失败\n");
return -1;
}
ucontext_t* coroutine_ctx2 = (ucontext_t*)malloc(sizeof(ucontext_t));
if (coroutine_ctx2 == NULL) {
printf("申请第2个协程的上下文失败\n");
return -1;
}
ucontext_t* coroutine_ctx3 = (ucontext_t*)malloc(sizeof(ucontext_t));
if (coroutine_ctx3 == NULL) {
printf("申请第3个协程的上下文失败\n");
return -1;
}
// 初始化协程这3个协程的上下文与一个主进程上下文
if (getcontext(&main_ctx)) {
printf("初始化主进程的上下文失败\n");
return -2;
}
if (getcontext(coroutine_ctx1)) {
printf("初始化协程1的上下文失败\n");
return -2;
}
if (getcontext(coroutine_ctx2)) {
printf("初始化协程2的上下文失败\n");
return -2;
}
if (getcontext(coroutine_ctx3)) {
printf("初始化协程3的上下文失败\n");
return -2;
}
// 再创建3个协程的栈空间以及1个主进程的栈空间
char* main_stack = (char*)malloc(STACKSIZE*sizeof(char));
if (main_stack == NULL) {
printf("申请第1个协程的栈空间失败\n");
return -3;
}
char* coroutine_stack1 = (char*)malloc(STACKSIZE*sizeof(char));
if (coroutine_stack1 == NULL) {
printf("申请第1个协程的栈空间失败\n");
return -3;
}
char* coroutine_stack2 = (char*)malloc(STACKSIZE*sizeof(char));
if (coroutine_stack2 == NULL) {
printf("申请第2个协程的栈空间失败\n");
return -3;
}
char* coroutine_stack3 = (char*)malloc(STACKSIZE*sizeof(char));
if (coroutine_stack3 == NULL) {
printf("申请第3个协程的栈空间失败\n");
return -3;
}
main_ctx.uc_stack.ss_sp = main_stack;
main_ctx.uc_stack.ss_size = STACKSIZE;
main_ctx.uc_link = NULL;
coroutine_ctx1->uc_stack.ss_sp = coroutine_stack1;
coroutine_ctx1->uc_stack.ss_size = STACKSIZE;
coroutine_ctx1->uc_link = &main_ctx;
coroutine_ctx2->uc_stack.ss_sp = coroutine_stack2;
coroutine_ctx2->uc_stack.ss_size = STACKSIZE;
coroutine_ctx2->uc_link = &main_ctx;
coroutine_ctx3->uc_stack.ss_sp = coroutine_stack3;
coroutine_ctx3->uc_stack.ss_size = STACKSIZE;
coroutine_ctx3->uc_link = &main_ctx;
// 将协程与特定函数绑定到一起,makecontext的第3个参数是表明后面跟着的参数数量,我只是将协程的上下文作为参数送过去了。
// 还可以给协程送更多的参数,但是由于只是demo,我就没有送。
makecontext(coroutine_ctx1, (void*)coroutine1, 1, coroutine_ctx1);
makecontext(coroutine_ctx2, (void*)coroutine2, 1, coroutine_ctx2);
makecontext(coroutine_ctx3, (void*)coroutine3, 1, coroutine_ctx3);
// 让出cpu,执行第1个协程,注意这里第一个参数必须是main_ctx,后面是你的协程上下文,执行了后程序会开始执行coroutine1这个函数
if (swapcontext(&main_ctx, coroutine_ctx1)) {
printf("main让出cpu给coroutine1失败\n");
}
printf("I am main process\n");
// 让出cpu,执行第2个协程
if (swapcontext(&main_ctx, coroutine_ctx2)) {
printf("main让出cpu给coroutine2失败\n");
}
printf("I am main process 2\n");
// 让出cpu,执行第1个协程
if (swapcontext(&main_ctx, coroutine_ctx1)) {
printf("main让出cpu给coroutine1失败\n");
}
printf("I am main process 3\n");
// 让出cpu,执行第3个协程
if (swapcontext(&main_ctx, coroutine_ctx3)) {
printf("main让出cpu给coroutine3失败\n");
}
printf("I am main process 4\n");
// 让出cpu,执行第2个协程
if (swapcontext(&main_ctx, coroutine_ctx2)) {
printf("main让出cpu给coroutine2失败\n");
}
printf("I am main process 5\n");
// 让出cpu,执行第3个协程
if (swapcontext(&main_ctx, coroutine_ctx3)) {
printf("main让出cpu给coroutine3失败\n");
}
printf("I am main process 6, finish\n");
free(coroutine_stack1);
free(coroutine_stack2);
free(coroutine_stack3);
free(coroutine_ctx1);
free(coroutine_ctx2);
free(coroutine_ctx3);
return 0;
}例子中只做了简单的切换,执行效果如下图:

可以看到,程序仅仅执行了简单的切换,但是都成功的在函数中途让出cpu,并且根据需求返回了,这足以说明程序切换是成功的。
通常情况下,该代码会结合epoll属性实现,某个协程执行过程中需要某个IO的数据,所以将IO注册到epoll后让出了cpu,具体细节小沃会在未来给大家讲解。
文章作者:沃航科技