Linux C语言实现协程功能

协程,作为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,具体细节小沃会在未来给大家讲解。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux环境下使用C语言编程,你可以创建各种类型的应用程序,包括系统工具、库、驱动程序等。C语言是系统级编程语言,对底层硬件操作有直接访问权限。以下是一些基本步骤和关键概念: 1. **设置开发环境**: - 安装gcc(GNU Compiler Collection)或clang,它们是Linux的主要C编译器。 - 可能需要配置路径环境变量,比如`export PATH=/usr/bin:$PATH`。 2. **创建源文件**: 使用文本编辑器(如vim, nano, gedit)创建`.c`文件,这是C代码的扩展名。 3. **编写代码**: - 基本结构包括`#include`头文件(如stdio.h, stdlib.h),声明变量和函数。 - 使用函数定义和调用,确保遵循C的内存管理规则,如栈分配和指针。 4. **编译链接**: - 使用`gcc yourfile.c -o yourprogram`命令编译,生成可执行文件。 - `-o`参数指定输出文件名。 5. **运行程序**: - `./yourprogram`命令运行你的C程序。 6. **错误处理和调试**: - 使用`printf()`和`scanf()`进行调试输出和输入。 - 使用gdb等调试工具进行深入调试。 7. **标准库和文件操作**: - `stdlib.h`提供内存管理函数(malloc, free)。 - `stdio.h`用于输入/输出(printf, scanf)。 - `fcntl.h`和`unistd.h`用于文件和系统调用。 **相关问题--:** 1. Linux C环境下的编译选项有哪些? 2. 如何在Linux中查看和理解C程序的错误日志? 3. 怎么使用C语言处理文件I/O操作?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值