├── .gitignore ├── CMakeLists.txt ├── coroutine_utils.hpp ├── generator.cpp ├── get_value.cpp ├── async.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-* 3 | 4 | .vscode 5 | build -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(stackless_coroutine_goto) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | find_package(Threads REQUIRED) 7 | 8 | add_executable(stackless_coroutine_generator generator.cpp) 9 | 10 | add_executable(stackless_coroutine_async async.cpp) 11 | target_link_libraries(stackless_coroutine_async Threads::Threads) 12 | 13 | add_executable(stackless_coroutine_get_value get_value.cpp) -------------------------------------------------------------------------------- /coroutine_utils.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xinyang on 2021/5/3. 3 | // 4 | 5 | #ifndef _COROUTINE_UTILS_HPP_ 6 | #define _COROUTINE_UTILS_HPP_ 7 | 8 | #define COROUTINE_BEGIN switch(_status) { case 0: ; 9 | #define COROUTINE_END default: ; } 10 | #define COROUTINE_YIELD(expr) do{ _status = __LINE__; return expr; case __LINE__: ; }while(0) 11 | #define COROUTINE_RETURN(expr) do{ _status = -1; return expr; }while(0) 12 | 13 | class coroutine_base { 14 | public: 15 | bool done() const { return _status < 0; } 16 | 17 | protected: 18 | int _status = 0; 19 | }; 20 | 21 | #endif /* _COROUTINE_UTILS_HPP_ */ 22 | -------------------------------------------------------------------------------- /generator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 使用无栈协程实现仿python的range生成器 3 | */ 4 | 5 | 6 | #include "coroutine_utils.hpp" 7 | #include 8 | 9 | template 10 | class range : public coroutine_base { 11 | public: 12 | explicit range(T end) : _begin(0), _end(end), _step(1) {} 13 | 14 | range(T begin, T end) : _begin(begin), _end(end), _step(1) {} 15 | 16 | range(T begin, T end, T step) : _begin(begin), _end(end), _step(step) {} 17 | 18 | T operator()() { 19 | COROUTINE_BEGIN 20 | for (_i = _begin; _i < _end; _i += _step) { 21 | COROUTINE_YIELD(_i); 22 | } 23 | COROUTINE_RETURN(-1); 24 | COROUTINE_END 25 | } 26 | 27 | private: 28 | T _begin, _end, _step; 29 | T _i; 30 | }; 31 | 32 | int main() { 33 | { 34 | std::cout << "range(10): " << std::endl; 35 | auto r = range(10); 36 | for (auto i = r(); !r.done(); i = r()) std::cout << i << " "; 37 | std::cout << std::endl; 38 | } 39 | { 40 | std::cout << "range(1.2, 12.3, 2.3): " << std::endl; 41 | auto r = range(1.2, 12.3, 2.3); 42 | for (auto i = r(); !r.done(); i = r()) std::cout << i << " "; 43 | std::cout << std::endl; 44 | } 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /get_value.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 从无栈协程的外部获取不同类型数据 3 | */ 4 | 5 | #include "coroutine_utils.hpp" 6 | #include 7 | 8 | class worker : public coroutine_base { 9 | public: 10 | worker() { 11 | // run to first yield 12 | (*this)(0); 13 | } 14 | 15 | template 16 | void operator()(T input) { 17 | COROUTINE_BEGIN 18 | 19 | std::cout << "waiting input for task 1..." << std::endl; 20 | COROUTINE_YIELD(); 21 | std::cout << "task 1, get input of [type: " << typeid(T).name() << " value:" << input << "]" << std::endl; 22 | 23 | std::cout << "waiting input for task 2..." << std::endl; 24 | COROUTINE_YIELD(); 25 | std::cout << "task 2, get input of [type: " << typeid(T).name() << " value:" << input << "]" << std::endl; 26 | 27 | std::cout << "waiting input for task 3..." << std::endl; 28 | COROUTINE_YIELD(); 29 | std::cout << "task 3, get input of [type: " << typeid(T).name() << " value:" << input << "]" << std::endl; 30 | 31 | std::cout << "waiting input for task 4..." << std::endl; 32 | COROUTINE_YIELD(); 33 | std::cout << "task 4, get input of [type: " << typeid(T).name() << " value:" << input << "]" << std::endl; 34 | 35 | COROUTINE_RETURN(); 36 | COROUTINE_END 37 | } 38 | }; 39 | 40 | int main() { 41 | auto w = worker(); 42 | 43 | // do something... 44 | w(1); 45 | 46 | // do something... 47 | w(2.); 48 | 49 | // do something... 50 | w("3"); 51 | 52 | // do something... 53 | w('4'); 54 | } 55 | -------------------------------------------------------------------------------- /async.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 使用无栈协程实现异步调用链 3 | */ 4 | 5 | 6 | #include "coroutine_utils.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std::chrono; 13 | 14 | class async_work : public coroutine_base, public std::enable_shared_from_this { 15 | public: 16 | async_work(std::string name) : _name(std::move(name)) {} 17 | 18 | void operator()() { 19 | COROUTINE_BEGIN 20 | COROUTINE_YIELD(run()); 21 | std::cout << _name << " work 1 done. back to thread: " << std::this_thread::get_id() << std::endl; 22 | COROUTINE_YIELD(run()); 23 | std::cout << _name << " work 2 done. back to thread: " << std::this_thread::get_id() << std::endl; 24 | COROUTINE_YIELD(run()); 25 | std::cout << _name << " work 3 done. back to thread: " << std::this_thread::get_id() << std::endl; 26 | COROUTINE_YIELD(run()); 27 | std::cout << _name << " work 4 done. back to thread: " << std::this_thread::get_id() << std::endl; 28 | COROUTINE_YIELD(run()); 29 | std::cout << _name << " work 5 done. back to thread: " << std::this_thread::get_id() << std::endl; 30 | COROUTINE_END 31 | } 32 | 33 | void run() { 34 | std::thread([](auto ptr) { 35 | std::cout << ptr->_name << " working..." << std::endl; 36 | std::this_thread::sleep_for(1s); 37 | (*ptr)(); 38 | }, shared_from_this()).detach(); 39 | } 40 | 41 | private: 42 | std::string _name; 43 | }; 44 | 45 | int main() { 46 | std::cout << "launch w1" << std::endl; 47 | auto w1 = std::make_shared("w1"); 48 | (*w1)(); 49 | 50 | std::this_thread::sleep_for(0.5s); 51 | { 52 | std::cout << "launch w2" << std::endl; 53 | auto w2 = std::make_shared("w2"); 54 | (*w2)(); 55 | } 56 | 57 | std::cout << "waiting..." << std::endl; 58 | std::this_thread::sleep_for(10s); 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于switch-case语句,20行代码实现无栈协程 2 | 3 | 无栈协程原理十分简单,可以看做是状态机的一种语法糖。之所以选择协程而非状态机,是由于协程的程序写法对程序员更加友好。我们可以利用switch-case语句,实现一个本质上是状态机,但在程序写法上却和通常意义上的协程相似的程序。再结合继承和宏,进行代码生成,消除大部分的重复代码就实现了一个最简单的协程。 4 | 5 | **本程序基于C++17实现。** 6 | 7 | ## 实现方法 8 | 9 | 以python的生成器语法为例,一个常见的写法如下: 10 | 11 | ```python 12 | def generator(x): 13 | for i in range(x): 14 | yield i 15 | ``` 16 | 17 | 我们可以将这个代码写成等价的C++语言基于goto的实现方式: 18 | 19 | ```c++ 20 | class generator{ 21 | public: 22 | explicit generator(int x) : x(x) {} 23 | 24 | int operator()(){ 25 | switch(_status){ 26 | case 0: goto status_0; 27 | case 1: goto status_1; 28 | default: ; // something wrong 29 | } 30 | 31 | status_0: 32 | for(i = 0; i < x; i++) { 33 | _status = 1; 34 | return i; 35 | status_1: ; 36 | } 37 | _status = -1; 38 | return -1; 39 | } 40 | 41 | bool done() const { return _status < 0; } 42 | private: 43 | int _status = 0; 44 | int x, i; 45 | }; 46 | ``` 47 | 48 | 可以看出,一个协程函数可以用一个类实现: 49 | 50 | * 将函数中所用到的**局部变量全部定义为类的成员变量**(很重要,否则程序运行可能不符合预期) 51 | * 每当调用operator()时,判断当前状态,并goto到特定位置 52 | * 每当出现一个yield语句,将其替换为:保存当前状态,函数返回,定义goto标签三部分。 53 | * done()函数用于判断当前协程是否已经结束。 54 | 55 | 由于该代码中,switch-case后直接接goto语句,所以我们可以将程序改写成以下形式: 56 | 57 | ```c++ 58 | class generator{ 59 | public: 60 | explicit generator(int x) : x(x) {} 61 | 62 | int operator()(){ 63 | switch(_status){ 64 | case 0: ; 65 | for(i = 0; i < x; i++) { 66 | _status = 1; 67 | return i; 68 | case 1: ; 69 | } 70 | _status = -1; 71 | return -1; 72 | default: ; // something wrong 73 | } 74 | } 75 | 76 | bool done() const { return _status < 0; } 77 | private: 78 | int _status = 0; 79 | int x, i; 80 | }; 81 | ``` 82 | 83 | 对于上述代码,我们可以很容易的发现其中的重复代码,可以使用继承和宏消除重复代码: 84 | 85 | ```c++ 86 | #define COROUTINE_BEGIN switch(_status) { case 0: ; 87 | #define COROUTINE_END default: ; } 88 | #define COROUTINE_YIELD(n, expr) do{ _status = n; return expr; case n: ; }while(0) 89 | #define COROUTINE_RETURN(expr) do{ _status = -1; return expr; }while(0) 90 | class coroutine_base { 91 | public: 92 | bool done() const { return _status < 0; } 93 | protected: 94 | int _status = 0; 95 | }; 96 | ``` 97 | 98 | 其中COROUTINE_BEGIN和COROUTINE_END包裹整个函数,用于提供switch-case的主体。 99 | 100 | COROUTINE_YIELD(n, expr)为yield语句,n为第几个yield语句(从1开始编号),expr为返回值。 101 | 102 | COROUTINE_RETURN(expr)代表协程结束,expr为返回值。 103 | 104 | class coroutine_base; 定义了状态变量,以及done()函数。 105 | 106 | 经过上述封装,我们的生成器协程可以简化成如下形式: 107 | 108 | ```c++ 109 | class generator : public coroutine_base { 110 | public: 111 | explicit generator(int x) : x(x) {} 112 | 113 | int operator()(){ 114 | COROUTINE_BEGIN 115 | for(i = 0; i < x; i++) { 116 | COROUTINE_YIELD(1, i); 117 | } 118 | COROUTINE_RETURN(-1); 119 | COROUTINE_END 120 | } 121 | 122 | private: 123 | int x, i; 124 | }; 125 | ``` 126 | 127 | 是不是已经很有协程的味道了! 128 | 129 | 我们还可以进一步简化这个程序,目前的程序COROUTINE_YIELD语句需要程序员手动进行编号,利用宏\__LINE__,我们可以简化这个步骤,详细实现方法见源码[coroutine_utils.cpp](coroutine_utils.cpp)。这样,程序可以进一步简化为: 130 | 131 | ```c++ 132 | class generator : public coroutine_base { 133 | public: 134 | explicit generator(int x) : x(x) {} 135 | 136 | int operator()(){ 137 | COROUTINE_BEGIN 138 | for(i = 0; i < x; i++) { 139 | COROUTINE_YIELD(i); 140 | } 141 | COROUTINE_RETURN(-1); 142 | COROUTINE_END 143 | } 144 | 145 | private: 146 | int x, i; 147 | }; 148 | ``` 149 | 150 | 到此,我们的这个代码和python中的已经十分相似了。 151 | 152 | --- 153 | 154 | 利用无栈协程,除了可以实现生成器外,还可以实现以下功能: 155 | 156 | * [get_value.cpp](get_value.cpp) 157 | * 与生成器从协程内部向外部返回数据相对应,协程同样可以从外部获取数据,结合C++模板,还可以使得每次获取到的数据类型不同。 158 | * [async.cpp](async.cpp) 159 | * 使用协程实现异步调用链 --------------------------------------------------------------------------------