C语言实现协程----初探

协程定义

协程,从编程的角度看来,可以理解为用户自己能控制和调度的线程,可以理解为用户级别的线程。一个线程可以有多个协程,一个进程也可以有多个协程。

协程实现

协程的实现有多种方式

  • setjmp和longjmp
  • sigsetjmp和siglongjmp
  • ucontext函数族

本次采用ucontext实现,思路是:在进行函数跳转前,将获取当前调用者的上下文,并在当前调用者的上下文的基础上进行修改,制作新的函数堆栈,然后切换到函数的上下文。当函数执行结束,切换到调用者的上下文,让调用者继续执行协程调度。

void makecontext(ucontext_t *ucp, void (*func)(), int argc, …);

makecontext() 函数修改 ucp 指向的用户线程上下文,该上下文必须先前已通过调用 getcontext() 进行初始化,并为其分配了堆栈。 上下文被修改,以便通过使用提供的参数(类型为 int)调用 func() 来继续执行。 argc 参数必须等于提供给 makecontext() 的附加参数的数量,也等于提供给 func() 的参数数量,否则会出现不可知行为。
ucp->uc_link 参数必须在调用 makecontext() 之前初始化,如果ucp->uc_link等于 NULL,则函数返回后进程退出; 否则,函数要返回时隐式调用 setcontext(ucp->uc_link)。

int getcontext(ucontext_t *ucp);

getcontext() 函数将当前线程的执行上下文保存在 ucp 指向的结构中。 这个保存的上下文可以稍后通过调用 setcontext() 来恢复。

int setcontext(const ucontext_t *ucp);

setcontext() 函数使先前保存的线程上下文成为当前线程上下文,即当前上下文丢失并且 setcontext() 不返回。 相反,在 ucp 指定的上下文中继续执行,该上下文必须先前已通过调用 getcontext()、makecontext() 进行初始化。

int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);

swapcontext() 函数将当前线程上下文保存在 *oucp 中,并使 *ucp 成为当前活动的上下文。

实现如下

fiber.h 协程接口头文件

#ifndef TEST_FIBER_H
#define TEST_FIBER_H

//定义一个任务
typedef void *FiberTask(void *);

/**
 * 初始化协程环境
 * @param maxFiberCount 最大协程数量
 * @return
 */
int InitFibersEnv(int maxFiberCount);

int FreeFibersEnv();

/**
 * 创建一个协程
 * @param task 传入一个任务
 * @param arg 任务的参数
 * @return
 */
int CreateFiber(FiberTask *task, void *arg);

/**
 * 协程调度
 * @return
 */
int ScheduleFiber();

#endif //TEST_FIBER_H

fiber.c 协程接口实现文件

#include "Fiber.h"

#define _XOPEN_SOURCE

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

#define StackSize (1024 * 10)

enum FiberStatusEnum {
    FIBER_IDLE,
    FIBER_RUNNING
};

/**
 * 协程环境
 */
struct FiberEnv {
    int fiberId;
    ucontext_t ucontext;
    FiberTask *fiberTask;
    void *taskArg;//任务参数

    char stackSpace[StackSize];

    short status;//状态

};

struct FibersEnv {
    int fiberCount;//协程数量
    struct FiberEnv **fiberEnvs;//协程环境
    ucontext_t ucontext;//主进程调度到上下文
};

struct FibersEnv *g_FiberEnv;

int InitFibersEnv(int maxFiberCount) {
    g_FiberEnv = calloc(1, sizeof(struct FibersEnv));
    g_FiberEnv->fiberCount = maxFiberCount;
    g_FiberEnv->fiberEnvs = (struct FiberEnv **) calloc(maxFiberCount, sizeof(struct FiberEnv *));
    for (int i = 0; i < maxFiberCount; ++i) {
        g_FiberEnv->fiberEnvs[i] = calloc(1, sizeof(struct FiberEnv));
        g_FiberEnv->fiberEnvs[i]->fiberId = i + 100;
    }
    return 0;
}

int FreeFibersEnv() {
    if (!g_FiberEnv) {
        return 0;
    }
    struct FiberEnv **p = g_FiberEnv->fiberEnvs;
    while (*p != NULL) {
        free(*p);
        p++;
    }
    free(g_FiberEnv);
    g_FiberEnv = NULL;
    return 0;
}

struct FiberEnv *FindFreeFiberSlot() {
    for (int i = 0; i < g_FiberEnv->fiberCount; ++i) {
        if (g_FiberEnv->fiberEnvs[i] && g_FiberEnv->fiberEnvs[i]->status == FIBER_IDLE) {
            return g_FiberEnv->fiberEnvs[i];
        }
    }
    return NULL;
}

void RunTask(uint32_t highPtr, uint32_t lowPtr) {
    uint64_t ptr = (uint64_t) highPtr << 32 | (uint64_t) lowPtr;
    struct FiberEnv *fiberEnv = (struct FiberEnv *) ptr;
    //执行任务
    fiberEnv->fiberTask(fiberEnv->taskArg);
//    printf("fiber[%d] exec ok!\n", fiberEnv->fiberId);
    fiberEnv->status = FIBER_IDLE;
    //任务执行完成之后,切换到调用者到上下文去执行
    swapcontext(&fiberEnv->ucontext, &g_FiberEnv->ucontext);
}

//创建一个协程
int CreateFiber(FiberTask *task, void *arg) {
    struct FiberEnv *fiberEnv = FindFreeFiberSlot();
    fiberEnv->status = FIBER_RUNNING;
    getcontext(&fiberEnv->ucontext);
    fiberEnv->ucontext.uc_stack.ss_size = StackSize;
    fiberEnv->ucontext.uc_stack.ss_sp = fiberEnv->stackSpace;

    fiberEnv->fiberTask = task;
    fiberEnv->taskArg = arg;

    uint32_t lowPtr = (uint64_t) fiberEnv;
    uint32_t highPtr = (uint64_t) fiberEnv >> 32;

    makecontext(&fiberEnv->ucontext, (void (*)()) RunTask, 2, highPtr, lowPtr);

    return 0;
}

int ScheduleFiber() {
    for (int i = 0; i < g_FiberEnv->fiberCount; ++i) {
        if (!g_FiberEnv->fiberEnvs[i]) {
            continue;
        }
        swapcontext(&g_FiberEnv->ucontext, &g_FiberEnv->fiberEnvs[i]->ucontext);
    }
    return 0;
}

main.c 协程接口测试文件

#include "Fiber.h"
#include <stdio.h>

void *PrintStrLine(void *str) {
    printf("%s\n", (char *) str);
    return NULL;
}


int main(int argc, char **argv) {
    InitFibersEnv(10);
    CreateFiber(PrintStrLine, "hello-world1");
    CreateFiber(PrintStrLine, "hello-world2");
    CreateFiber(PrintStrLine, "hello-world3");
    ScheduleFiber();
    FreeFibersEnv();

    printf("exec ok...\n");
    return 0;
}

执行

gcc Fiber.c main.c -o testFiber

打印如下

hello-world1
hello-world2
hello-world3
exec ok...

注意

  • 可以注意到,makecontext为协程入口函数绑定上下文的时候,当需要为函数传递指针等变量的时候,因为makecontext是可变参,函数内部统一用4个字节去处理(有些系统可能已经做到了兼容)。而指针变量是8个字节(64位系统),故需要将指针变量拆分成高位和低位去处理。
  • 本文的实验环境位mac,mac默认的ucontext接口已经废弃,需要定义_XOPEN_SOURCE宏强制开启
  • 在用mac实验的时候,如果是封装在C++的类函数中,并使用了C++的集合,有些操作会出现奔溃问题,暂时理不清为什么。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值