最近在学习lua的过程中发现lua居然有个东西叫协程(协同coroutine),虽然以前就听过这个概念,但没有结合实践的一些理解。
开始今天的文章前,首先需要学习下面几篇文章。
漫画-什么是协程?
lua协同程序
difference between a coroutine and a thread

linux下有ucontext族函数,可以用于实现协程。
我所理解的ucontext族函数
ucontext族函数详解

Segment Fault例子:

#include <stdio.h>

void ping();
void pong();

void ping(){
    int a = 0;
    printf("ping,addr of a = %p\n",&a);
    pong();
}

void pong(){
    int b = 0;
    printf("pong,addr of b = %p\n",&b);
    ping();
}

int main(int argc, char *argv[]){
    ping();
    return 0;
}

将上述代码编译运行,执行结果:

ping,addr of a = 0x7ffc456b2024
pong,addr of b = 0x7ffc456b2004
ping,addr of a = 0x7ffc456b1fe4
pong,addr of b = 0x7ffc456b1fc4
ping,addr of a = 0x7ffc456b1fa4
pong,addr of b = 0x7ffc456b1f84
ping,addr of a = 0x7ffc456b1f64
pong,addr of b = 0x7ffc456b1f44
ping,addr of a = 0x7ffc456b1f24
pong,addr of b = 0x7ffc456b1f04
ping,addr of a = 0x7ffc456b1ee4
pong,addr of b = 0x7ffc456b1ec4
ping,addr of a = 0x7ffc456b1ea4
pong,addr of b = 0x7ffc456b1e84
ping,addr of a = 0x7ffc456b1e64
pong,addr of b = 0x7ffc456b1e44
ping,addr of a = 0x7ffc456b1e24
pong,addr of b = 0x7ffc456b1e04
ping,addr of a = 0x7ffc456b1de4
pong,addr of b = 0x7ffc456b1dc4
ping,addr of a = 0x7ffc456b1da4
pong,addr of b = 0x7ffc456b1d84
ping,addr of a = 0x7ffc456b1d64
pong,addr of b = 0x7ffc456b1d44
ping,addr of a = 0x7ffc456b1d24
pong,addr of b = 0x7ffc456b1d04
ping,addr of a = 0x7ffc456b1ce4
pong,addr of b = 0x7ffc456b1cc4
ping,addr of a = 0x7ffc456b1ca4
pong,addr of b = 0x7ffc456b1c84
ping,addr of a = 0x7ffc456b1c64
pong,addr of b = 0x7ffc456b1c44
ping,addr of a = 0x7ffc456b1c24
pong,addr of b = 0x7ffc456b1c04
ping,addr of a = 0x7ffc456b1be4
pong,addr of b = 0x7ffc456b1bc4
ping,addr of a = 0x7ffc456b1ba4
pong,addr of b = 0x7ffc456b1b84
Segmentation fault (core dumped)

运行10几秒左右后,程序core dumped,无限递归调用,栈耗尽。可以看到a与b的地址每次调用都不相同。

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

#define MAX_COUNT (1<<30)

static ucontext_t uc[3];
static int count = 0;

void ping();
void pong();

void ping(){
    int a = 10;
    while(count < MAX_COUNT){
        printf("ping %d,addr of a = %p\n", ++count,&a);
        // yield to pong
        swapcontext(&uc[1], &uc[2]); // 保存当前context于uc[1],切换至uc[2]的context运行
    }
}

void pong(){
    int b = 10;
    while(count < MAX_COUNT){
        printf("pong %d,addr of b = %p\n", ++count,&b);
        // yield to ping
        swapcontext(&uc[2], &uc[1]);// 保存当前context于uc[2],切换至uc[1]的context运行
    }
}

char st1[8192];
char st2[8192];

int main(int argc, char *argv[]){
   
    // initialize context
    getcontext(&uc[1]);
    getcontext(&uc[2]);

    uc[1].uc_link = &uc[0]; //表示uc[1]运行完成后,会跳至uc[0]指向的context继续运行
    uc[1].uc_stack.ss_sp = st1; // 设置新的堆栈
    uc[1].uc_stack.ss_size = sizeof st1;
    makecontext (&uc[1], ping, 0);

    uc[2].uc_link = &uc[0]; //表示uc[2]运行完成后,会跳至uc[0]指向的context继续运行
    uc[2].uc_stack.ss_sp = st2; // 设置新的堆栈
    uc[2].uc_stack.ss_size = sizeof st2;
    makecontext (&uc[2], pong, 0);

    // start ping-pong
    swapcontext(&uc[0], &uc[1]); // 将当前context信息保存至uc[0],跳转至uc[1]保存的context去执行
    
    //swapcontext函数会将当前点的信息保存在uc[0]中,当然我们没有设置的话,默认的堆栈一定是主堆栈啦

    return 0;
}

编译运行结果:

ping 332825,addr of a = 0x561022043b14
pong 332826,addr of b = 0x561022041b14
ping 332827,addr of a = 0x561022043b14
pong 332828,addr of b = 0x561022041b14
ping 332829,addr of a = 0x561022043b14
pong 332830,addr of b = 0x561022041b14
ping 332831,addr of a = 0x561022043b14
pong 332832,addr of b = 0x561022041b14
ping 332833,addr of a = 0x561022043b14
pong 332834,addr of b = 0x561022041b14
ping 332835,addr of a = 0x561022043b14
pong 332836,addr of b = 0x561022041b14
ping 332837,addr of a = 0x561022043b14
pong 332838,addr of b = 0x561022041b14
ping 332839,addr of a = 0x561022043b14
pong 332840,addr of b = 0x561022041b14
ping 332841,addr of a = 0x561022043b14
pong 332842,addr of b = 0x561022041b14
ping 332843,addr of a = 0x561022043b14
pong 332844,addr of b = 0x561022041b14
ping 332845,addr of a = 0x561022043b14
pong 332846,addr of b = 0x561022041b14
ping 332847,addr of a = 0x561022043b14
pong 332848,addr of b = 0x561022041b14
ping 332849,addr of a = 0x561022043b14

可以看到a b的地址每次都相同,而不是每次函数调用,使用新的栈地址空间。

#include <stdio.h>
#include <ucontext.h> 
#include <unistd.h> 
int main(int argc, char *argv[]) 
{ 
  ucontext_t context; 
  getcontext(&context); 
  puts("Hello world"); 
  sleep(1); 
  setcontext(&context); 
  return 0; 
}

getcontext, setcontext - get or set the user context(man setcontext)

这个函数会不断地打印Hello,world。因为上面的getcontext函数将那个点的上下文信息保存到了context中,下面调用setcontext会返回到记录的点处继续执行,因此也就出现了不断地输出。
有了上面的一些理解,我们可以学习一些开源库。(按照代码量从低到高学习)
cloudwu/coroutine
libgo
libco