协程实现
C
1. 利用 C 语言的 setjmp 和 longjmp,函数中使用 static local 的变量来保存协程内部的数据。
函数原型:
1 2
| int setjmp(jmp_buf envbuf); void longjmp(jmp_buf envbuf, int val);
|
先调用setjmp,用变量envbuf记录当前的位置,然后调用longjmp,返回envbuf所记录的位置,并使setjmp的返回值为val。使用longjmp后,envbuf的内容会被销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 1 #include <stdio.h> 2 #include <setjmp.h> 3 4 jmp_buf buf; 5 6 banana() 7 { 8 printf("in banana() \n"); 9 longjmp(buf,1); 10 printf("you'll never see this,because i longjmp'd"); 11 } 12 13 main() { 15 if(setjmp(buf)) 16 printf("back in main\n"); 17 else 18 { 19 printf("first time through\n"); 20 banana(); } }
|
打印结果:
1 2 3 4 5
| first time through
in banana()
back in main
|
2、利用C语言语法switch-case的技巧来实现
设置一个标识符,改变标识符的值,通过switch-case对标识符值的判断操纵各协程函数轮流执行。
每个协程函数可配一个结构体,保存栈内容和状态机。
代码:https://github.com/georgeredinger/protothreads
3、使用汇编代码来切换上下文(实现c协程) 。
构建一个结构体保存栈内容和当前位置等上下文信息,利用汇编语言的跳转实现协程功能。
详情见:https://www.cnblogs.com/heluan/p/9899824.html
4、利用操作系统提供的接口:Linux的ucontext,Windows的Fiber。(云风的coroutine)
ucontext: makecontext() 创建上下文
getcontext() 读取上下文
setcontext() 设置上下文
swapcontext() 跳转上下文
Fiber(纤程):ConverThreadToFiber() 从当前线程进入纤程
CreateFiber() 创建新纤程
SwitchToFiber() 切换到纤程
DeleteFiber() 删除纤程
如果删除当前纤程,会导致它所在的线程退出
操作系统的接口函数本身,提供了保存栈内容的功能。
C++
说到 c++ 上的协程,boost 里其实已经有相关的实现了,不过接口上看用起来有些麻烦,单纯从语法上来说,我觉得 Lua 的协程最简洁易用了,概念上也比较直接,为什么不做一个类似的呢?所以我就打算照着 Lua 来山寨一个,只需要支持四个接口就够了:
1 2 3 4
| create coroutine run/resume coroutine Yield running corouinte IsCoroutineAlive
|
存与恢复上下文
实现协程/线程,最麻烦莫过于保存和切换上下文了,好在 makecontext,swapcontext 这几个函数相当好用,已经完全帮忙解决了这个难题:makecontext 可以帮我们建立起协程的上下文,swapcontext 则可以切换不同的上下文,从而实现那种把当前函数暂时停住,切换出去执行别的函数然后再切换回来继续执行的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <iostream> #include <ucontext.h> using namespace std;
static char g_stack[2048]; static ucontext_t ctx,ctx_main;
void func() { cout << "enter func" << endl;
swapcontext(&ctx, &ctx_main);
cout << "func1 resume from yield" << endl; }
int main() { getcontext(&ctx); ctx.uc_stack.ss_sp = g_stack; ctx.uc_stack.ss_size = sizeof g_stack; ctx.uc_link = &ctx_main; makecontext(&ctx, func, 0);
cout << "in main, before coroutine starts" << endl;
swapcontext(&ctx_main, &ctx);
cout << "back to main" << endl;
swapcontext(&ctx_main, &ctx); cout << "back to main again" << endl; return 0; }
|
如上代码所示,显然我们只要简单包装一下 swapcontext,很容易就可以实现 Yield 和 Resume,有了它们的帮助协程做起来就容易多了。
使用与实现
在使用 makecontext,swapcontext 的基础上,参看这里,代码写下来总共才200多行,出乎意料的简单,用起来也很方便了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include "coroutine.h"
#include <iostream>
using namespace std;
CoroutineScheduler* sched = NULL;
void func1(void* arg) { uintptr_t ret; cout << "function1 a now!,arg:" << arg << ", start to yield." << endl; ret = sched->Yield((uintptr_t)"func1 yield 1"); cout << "1.fun1 return from yield:" << (const char*)ret << endl; ret = sched->Yield((uintptr_t)"func1 yield 2"); cout << "2.fun1 return from yield:" << (const char*)ret << ", going to stop" << endl;
}
void func2(void* s) { cout << "function2 a now!, arg:" << s << ", start to yield." << endl; const char* y = (const char*)sched->Yield((uintptr_t)"func2 yield 1"); cout << "fun2 return from yield:" << y <<", going to stop" << endl; }
int main() { sched = new CoroutineScheduler();
bool stop = false; int f1 = sched->CreateCoroutine(func1, (void*)111); int f2 = sched->CreateCoroutine(func2, (void*)222);
while (!stop) { stop = true; if (sched->IsCoroutineAlive(f1)) { stop = false; const char* y1 = (const char*)sched->ResumeCoroutine(f1, (uintptr_t)"resume func1"); cout << "func1 yield:" << y1 << endl; }
if (sched->IsCoroutineAlive(f2)) { stop = false; const char* y2 = (const char*)sched->ResumeCoroutine(f2, (uintptr_t)"resume func2"); cout << "func2 yield:" << y2 << endl; } }
delete sched; return 0; }
|
如上所示,Yield 里传的参数会在调用 Resume 时被返回,同理 Resume 里的第二个参数,会在 Yield 里被返回,这种机制也是模仿 Lua 来的,有些时候可以用来在协程间传递一些参数,很方便,看起来也挺酷的,但在实现上却相当地简洁,核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| void CoroutineScheduler::SchedulerImpl::Schedule(void* arg) { assert(arg); SchedulerImpl* sched = (SchedulerImpl*) arg;
int running = sched->running_;
coroutine* cor = sched->id2routine_[running]; assert(cor);
cor->func(cor->arg);
sched->running_ = -1; cor->status = CO_FINISHED; }
uintptr_t CoroutineScheduler::SchedulerImpl::ResumeCoroutine(int id, uintptr_t y) { coroutine* cor = id2routine_[id]; if (cor == NULL || cor->status == CO_RUNNING) return 0;
cor->yield = y; switch (cor->status) { case CO_READY: { getcontext(&cor->cxt);
cor->status = CO_RUNNING; cor->cxt.uc_stack.ss_sp = cor->stack; cor->cxt.uc_stack.ss_size = stacksize_; cor->cxt.uc_link = &mainContext_;
running_ = id; makecontext(&cor->cxt, (void (*)())Schedule, 1, this); swapcontext(&mainContext_, &cor->cxt); } break; case CO_SUSPENDED: { running_ = id; cor->status = CO_RUNNING; swapcontext(&mainContext_, &cor->cxt); } break; default: assert(0); }
uintptr_t ret = cor->yield;
if (running_ == -1 && cor->status == CO_FINISHED) DestroyCoroutine(id);
return ret; }
uintptr_t CoroutineScheduler::SchedulerImpl::Yield(uintptr_t y) { if (running_ < 0) return 0;
int cur = running_; running_ = -1;
coroutine* cor = id2routine_[cur];
cor->yield = y; cor->status = CO_SUSPENDED;
swapcontext(&cor->cxt, &mainContext_); return cor->yield; }
|
ref: https://www.cnblogs.com/catch/p/3617962.html