美文网首页
设计一个简易的协程模型-C

设计一个简易的协程模型-C

作者: 简单方式 | 来源:发表于2019-11-03 20:11 被阅读0次

协程概念

首先都知道协程的核心点就是 轻量级用户态切换速度快灵活相比较(进程\线程)更节省资源 ,但是如何能实现由用户态自己去调度切换呢, 实际上就是在我们每次调用中断的时候,自动保存当前执行函数的调用栈上下文和跳转地址,以便在下次执行的时候自动恢复到上次执行点,那如果要从底层理解的话就是在不断切换/赋值寄存器 RSP RIP 两个指令的地址, 最终由这两个指令最终去完成函数调用栈的恢复和执行.

实现一个协程模型

我们都知道协程这个概念了,那如何自己去搞一个呢,比如Go语言天生支持,而且实现的协程是目前完成度比较好的,但如果用 C 或者 PHP 怎么去实现一个这种特性呢,如果是 PHP 可能还会有 yield 这个关键字能实现类似的功能,但是如果用 C 的话就比困难了,因为根本没有这个关键字和功能支持,需要我们自己去写 yield 这个关键字,所以需要借助一些底层库去完成, 这里采用的是 ucontext 协程库去实现,这也是 C 实现协程用得最多的方案,而这个库提供的功能就是保存上下文, 切换上下文, 设置上下文 基于这个库之后我们在加以封装,开发一个调度管理器用以调度就可以了.

实现设计图

协程模型设计图

提供公共方法

static void task ((void*)()) 注册需要调度的函数

static void yield() 中断并保存上下文

static int run() 开始运行并调度管理注册的函数

实现代码

#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <unistd.h>

#define RUN_START 1
#define RUN_END   0 

static int stack_size = 1024;

static int pid = 1;

typedef void (*funType)(void);

typedef struct env{
     funType func;
     ucontext_t ucon;
     struct env *next, *prev;
     int task_id;
     int run_status;        
}env;

static env* current_list = NULL;

static ucontext_t run_env;

static void exec(){
     current_list->func();
     current_list->run_status = RUN_END;
     setcontext(&run_env);      
}

static void task(funType func){
    env* node = (env*)malloc(sizeof(env));        
    
    node->task_id    = pid++;
    node->func       = func;
    node->run_status = RUN_START;

    if(current_list){
            current_list->prev->next = node;
            node->next               = current_list;
            node->prev               = current_list->prev;
            current_list->prev       = node;   
    }else{
            node->next   = node;
            node->prev   = node;        
            current_list = node;
    }
    
    getcontext(&node->ucon);
    node->ucon.uc_stack.ss_sp   = malloc(stack_size); 
    node->ucon.uc_stack.ss_size = stack_size;
    makecontext(&node->ucon, exec, 0);      
}

static int run(){
    struct env* current_node = current_list;
    while(current_list){
    printf("task_id=%d -- run_status=%d \r\n",current_list->task_id, current_list->run_status);
    swapcontext(&run_env, &current_list->ucon);
    printf("task_id=%d -- run_status=%d \r\n",current_list->task_id, current_list->run_status);
    sleep(1);
    //如果函数调用栈执行完毕则释放
    if(current_list->run_status == RUN_END){
            current_node = current_list;
            if(current_node == current_list->next){
                current_list = NULL;
            }else{          
                current_list->prev->next = current_list->next;
                current_list->next->prev = current_list->prev;
                current_list         = current_list->next; 
            }
            free(current_node->ucon.uc_stack.ss_sp);
            free(current_node);
     }else{
            current_list = current_list->next; 
     }
  }
}

static void yield(){
    swapcontext(&current_list->ucon,&run_env);
} 



int fun1(){
     printf("fun1-a\r\n");
     yield();
     printf("fun1-b\r\n");  
     return 0;          
}
void fun2(){
     printf("fun2\r\n");
}
void fun3(){
     printf("fun3\r\n");
}


int main(){
    
    task((void(*)(void))fun1);
    task(fun2);
    //task(fun3);
    run();
    
    return 0;
   
} 

运行结果

首先我们注册两个函数 fun1() fun2() 然后进行执行,会依次打印 fun1-a , fun2 , fun1-b

运行结果

执行环境

Linux 027a36236b38 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

结束

以上仅供学习,并不在生产环境有任何价值.

相关文章

网友评论

      本文标题:设计一个简易的协程模型-C

      本文链接:https://www.haomeiwen.com/subject/xzkpkctx.html