最近在学习lua的过程中发现lua居然有个东西叫协程(协同coroutine),虽然以前就听过这个概念,但没有结合实践的一些理解。
开始今天的文章前,首先需要学习下面几篇文章。
linux下有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会返回到记录的点处继续执行,因此也就出现了不断地输出。
有了上面的一些理解,我们可以学习一些开源库。(按照代码量从低到高学习)