赞
踩
协程就是用户态线程内可以被异步执行的函数。用来在用户态下解决异步问题。
普通函数:每次调用只能从第一条语句开始执行直到某个出口结束。
协程函数:协程函数交出控制权后,可以再次从交出控制权的下一语句开始执行。
在协程的叫法出现以前,处理异步问题一般会使用操作系统提供的系统级API来解决异步问题。系统级的API都是大多采用回调函数方式实现。后来人们觉得使用异步回调形式的API比较麻烦,就开始提供异步调用库或者语言级支持。并且起了个优雅的名字–协同程序。
协程不是线程。协程包含在线程内。协程在用户态,由用户控制。协程的切换比线程切换效率高。
同一个线程在一个时间点最多只能跑一个协程;在同一个线程中,协程的运行是穿行的[穿插运行(同一个函数内非顺序运行),一般认为不同与串行(顺序运行)]。所以没有数据争用(data race),也不需要锁。
原则上,应该尽量避免将同一个协程的主体,放到不同的线程中同时执行。因为这样有很大概率发生数据争用(data race)。[其实这种情况下就是线程的数据争用问题]。所以我们应该在线程中讨论协程;而不是在进程中讨论协程。
协程根据实现方式不同,分为有栈协程(stackful coroutine)和无栈协程(stackless coroutine)。
有栈协程可通过操作系统提供的系统调用实现;无栈协程需要语言标准和编译器支持。
OS | 有栈协程系统调用 |
---|---|
Linux | getcontext,setcontext,makecontext,swapcontext |
Windows | CreateFiber,ConvertFiberToThread,SwitchToFiber |
微软拟提的C++20标准中(目前是ts,即:<experimental/coroutine>)的协程属于stackless coroutine。
1:首次调用协程函数,会从堆中分配一个协程上下文,调用方的返回地址、入口函数、交出控制权等信息保存在协程上下文中。
2:当协程中途交出控制权后,协程上下文不会被删除(相当于函数退出之后,上下文环境还被保存,类比线程切换)。
3:当协程再次获得控制权后,会自动从协程上下文中恢复调用环境,然后从上一次交出控制权的下一条语句继续执行(加载目标协程环境,类比线程切换)。
4:协程函数返回(非中途交出控制权)后,协程上下文将被删除。
5:若再次调用协程函数,视为首次调用。
1:每个协程都有一个预先分配的调用栈(Call Stack)。
2:每个协程都属于且仅属于创建它的线程。
3:一个线程可以包含多个协程。
4:线程本身也可以是一个协程,成为主协程(Primary Coroutine)。
5:协程必须主动交出控制权,否则同一线程的其它协程均无法获得执行机会。
6:协程执行路径上,任何被调用的函数均可在任何位置交出控制权。
7:如果允许协程把控制权交给同一线程的其它协程,则称为对称协程(Symmetry Coroutines)。如果只允许协程把控制权交给主协程,主协程作为调度器,负责分配执行权,则称为非对称协程(Asymmetry Coroutines)。
8:属于同一线程的多个协程之间没有数据争用(Data Race)问题。
9:无需修改语言标准和编译器,利用系统调用即可实现。
1:每个协程的执行环境,仅需包含调用栈(Call Stack)的顶层栈帧(Top Call Stack Frame),而非整个调用栈,因而空间开销极小。
2:协程执行路径上,只有特定语句才能交出控制权。
3:无需协程调度器。
4:调用协程函数时,同一协程函数的不同部分,有可能在不同的线程环境中执行。因此需要处理好数据争用(Data Race)问题。这个问题实际是线程间数据争用问题。
5:需要语言标准和编译器支持。
有栈协程和无栈协程对比,有栈协程的最大缺陷是保存调用栈的开销大,尤其协程较多且切换频繁时;
无栈协程不但具有有栈协程的所有优点,而且空间开销极低;唯一不足就是需要语言标准和编译器支持。
如果在C++20的一个函数体内包含co_await、co_yield、co_return中任何一个关键字,那么这个函数就是一个coroutine。其中:
co_await:挂起当前的coroutine。
co_return:从当前coroutine返回一个结果。
co_yield:返回一个结果并且挂起当前的coroutine。
一个coroutine必定包含Promise和Awaitable两个部分。
协程通过Promise和Awaitable接口来规范实现。实现最简单的协程需要用到其中的8个(5个Promise的函数和3个Awaitable的函数)。
如果要实现形如co_await xxxxx;
的协程调用格式, xxxxx
就必须实现Awaitable
。co_await
是一个新的运算符。Awaitable主要有3个函数:
await_ready:返回Awaitable实例是否已经ready。协程开始会调用此函数,如果返回true,表示你想得到的结果已经得到了,协程不需要执行了。所以大部分情况这个函数的实现是要return false。
await_suspend:挂起awaitable。该函数会传入一个coroutine_handle类型的参数。这是一个由编译器生成的变量。在此函数中调用handle.resume(),就可以恢复协程。
await_resume:当协程重新运行时,会调用该函数。这个函数的返回值就是co_await运算符的返回值。
启用ts中的Coroutine功能,需要vs2019中做如下配置:
配置属性==>常规==>C++语言标准,预览-最新C++工作草案中的功能(std:c++latest)
配置属性==>C/C++==>命令行,添加/await
并且在代码中加入实验库#include <experimental/coroutine>
。
下面的demo代码收集在:
https://github.com/5455945/cpp_demo/tree/master/C%2B%2B20
这是 https://github.com/franktea/temp/blob/master/uncategorized/coroutine.md 中实现的。在这里对每个函数断点后,调试,对理解协程流程很有帮助。再结合C++ 协程介绍[译]中的图形解释,会很有帮助。
// https://github.com/franktea/temp/blob/master/uncategorized/co_vs_callback.cpp // https://github.com/franktea/temp/blob/master/uncategorized/coroutine.md #include <iostream> #include <thread> #include <experimental/coroutine> #include <chrono> #include <functional> #include "co_vs_callback.h" // clang++ -std=c++2a -fcoroutines-ts -lstdc++ co_vs_callback.cpp using call_back = std::function<void(int)>; void Add100ByCallback(int init, call_back f) // 异步调用 { std::thread t([init, f]() { std::this_thread::sleep_for(std::chrono::seconds(5)); std::cout << "Add100ByCallback: " << init << std::endl; f(init + 100); }); t.detach(); } struct Add100Awaitable { Add100Awaitable(int init) :init_(init) {} bool await_ready() const { return false; } int await_resume() { return result_; } void await_suspend(std::experimental::coroutine_handle<> handle) { auto f = [handle, this](int value) mutable { result_ = value; handle.resume(); }; Add100ByCallback(init_, f); // 调用原来的异步调用 } int init_; int result_; }; struct Task { struct promise_type { auto get_return_object() { return Task{}; } auto initial_suspend() { return std::experimental::suspend_never{}; } auto final_suspend() { return std::experimental::suspend_never{}; } void unhandled_exception() { std::terminate(); } void return_void() {} }; }; Task Add100ByCoroutine(int init, call_back f) { int ret = co_await Add100Awaitable(init); //ret = co_await Add100Awaitable(ret); //ret = co_await Add100Awaitable(ret); f(ret); } void co_vs_callback() { //Add100ByCallback(5, [](int value) { std::cout << "get result: " << value << "\n"; }); Add100ByCoroutine(10, [](int value) { std::cout << "get result from coroutine1: " << value << "\n"; }); //Add100ByCoroutine(20, [](int value) { std::cout << "get result from coroutine2: " << value << "\n"; }); //Add100ByCoroutine(30, [](int value) { std::cout << "get result from coroutine3: " << value << "\n"; }); //Add100ByCoroutine(40, [](int value) { std::cout << "get result from coroutine4: " << value << "\n"; }); std::this_thread::sleep_for(std::chrono::seconds(50)); }
这个demo来自黑山是不是山黑
的C++ coroutine
// https://www.cnblogs.com/heishanlaoy/p/11760368.html #include "co_vs_await.h" #include <iostream> #include <experimental/coroutine> using namespace std; template<class T> struct test { // inner types struct promise_type; using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias // functions test(handle_type h) :handle(h) { cout << "# Created a Test object\n"; } test(const test& s) = delete; test& operator=(const test&) = delete; test(test&& s) :handle(s.handle) { s.handle = nullptr; } test& operator=(test&& s) { handle = s.handle; s.handle = nullptr; return *this; } ~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); } T get() { cout << "# Got return value\n"; if (!(this->handle.done())) { handle.resume(); //resume return handle.promise().value; } } struct promise_type { promise_type() { cout << "@ promise_type created\n"; } ~promise_type() { cout << "@ promise_type died\n"; } auto get_return_object() //get return object { cout << "@ get_return_object called\n"; return test<T>{handle_type::from_promise(*this)};// pass handle to create "return object" } auto initial_suspend() // called before run coroutine body { cout << "@ initial_suspend is called\n"; // return std::experimental::suspend_never{}; // dont suspend it return std::experimental::suspend_always{}; } auto return_void() // called when just before final_suspend, conflict with return_value { cout << "@ return_void is called\n"; return std::experimental::suspend_never{}; // dont suspend it //return std::experimental::suspend_always{}; } auto yield_value(int t) // called by co_yield() { std::cout << "yield_value called\n"; value = t; return std::experimental::suspend_always{}; } auto final_suspend() // called at the end of coroutine body { cout << "@ final_suspend is called\n"; return std::experimental::suspend_always{}; } void unhandled_exception() //exception handler { std::exit(1); } //T await_transform() {} // data T value; }; // member variables handle_type handle; }; struct AwaiableObj { int a; AwaiableObj() :a(0) {} bool await_ready() { cout << "@@ await_ready called\n"; return true; } auto await_suspend(std::experimental::coroutine_handle<> awaiting_handle) { cout << "@@ await_suspend called\n"; // return ; // return true; return false; // return awaiting_handle; } auto await_resume() { cout << "@@ await_resume called\n"; return a++; } }; test<int> await_routine() { auto a = AwaiableObj{}; for (int i = 0; i < 5; i++) { auto v = co_await a; co_yield v; } } void co_vs_await() { auto a = await_routine(); auto b = a.get(); cout << "value is " << b << endl; b = a.get(); cout << "value is " << b << endl; b = a.get(); cout << "value is " << b << endl; b = a.get(); cout << "value is " << b << endl; b = a.get(); cout << "value is " << b << endl; }
这个demo来自黑山是不是山黑
的C++ coroutine
// https://www.cnblogs.com/heishanlaoy/p/11760368.html #include "co_vs_return.h" #include <iostream> #include <experimental/coroutine> using namespace std; template<class T> struct test { // inner types struct promise_type; using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias // functions test(handle_type h) :handle(h) { cout << "# Created a Test object\n"; } test(const test& s) = delete; test& operator=(const test&) = delete; test(test&& s) :handle(s.handle) { s.handle = nullptr; } test& operator=(test&& s) { handle = s.handle; s.handle = nullptr; return *this; } ~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); } T get() { cout << "# Got return value\n"; if (!(this->handle.done())) { handle.resume(); //resume return handle.promise().value; } } struct promise_type { promise_type() { cout << "@ promise_type created\n"; } ~promise_type() { cout << "@ promise_type died\n"; } auto get_return_object() //get return object { cout << "@ get_return_object called\n"; return test<T>{handle_type::from_promise(*this)};// pass handle to create "return object" } auto initial_suspend() // called before run coroutine body { cout << "@ initial_suspend is called\n"; // return std::experimental::suspend_never{}; // dont suspend it return std::experimental::suspend_always{}; } auto return_value(T v) // called when there is co_return expression { cout << "@ return_value is called\n"; value = v; return std::experimental::suspend_never{}; // dont suspend it //return std::experimental::suspend_always{}; } auto final_suspend() // called at the end of coroutine body { cout << "@ final_suspend is called\n"; return std::experimental::suspend_always{}; } void unhandled_exception() //exception handler { std::exit(1); } // data T value; }; // member variables handle_type handle; }; test<int> return_coroutine() { std::cout << "start return_coroutine\n"; co_return 1; co_return 2; // will never reach here } void co_vs_return() { auto a = return_coroutine(); cout << "created a corutine, try to get a value\n"; int an = a.get(); cout << "value is " << an << endl; an = a.get(); cout << "value is " << an << endl; }
这个demo来自黑山是不是山黑
的C++ coroutine
// https://www.cnblogs.com/heishanlaoy/p/11760368.html #include "co_vs_yield.h" #include <iostream> #include <experimental/coroutine> using namespace std; struct test { // inner types struct promise_type; using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias // functions test(handle_type h) :handle(h) { cout << "# Created a Test object\n"; } test(const test& s) = delete; test& operator=(const test&) = delete; test(test&& s) :handle(s.handle) { s.handle = nullptr; } test& operator=(test&& s) { handle = s.handle; s.handle = nullptr; return *this; } ~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); } int current_value() { return handle.promise().value; } bool move_next() { handle.resume(); return !handle.done(); } struct promise_type { promise_type() { cout << "@ promise_type created\n"; } ~promise_type() { cout << "@ promise_type died\n"; } auto get_return_object() //get return object { cout << "@ get_return_object called\n"; return test{ handle_type::from_promise(*this) };// pass handle to create "return object" } auto initial_suspend() // called before run coroutine body { cout << "@ initial_suspend is called\n"; return std::experimental::suspend_never{}; // dont suspend it //return std::experimental::suspend_always{}; } auto return_void() // called when just before final_suspend, conflict with return_value { cout << "@ return_void is called\n"; return std::experimental::suspend_never{}; // dont suspend it //return std::experimental::suspend_always{}; } auto yield_value(int t) // called by co_yield() { std::cout << "yield_value called\n"; value = t; return std::experimental::suspend_always{}; } auto final_suspend() // called at the end of coroutine body { cout << "@ final_suspend is called\n"; return std::experimental::suspend_always{}; } void unhandled_exception() // exception handler { std::exit(1); } // data int value; }; // member variables handle_type handle; }; test yield_coroutine(int count) { std::cout << "start yield_coroutine\n"; for (int i = 0; i < count; i++) co_yield i * 2; } void co_vs_yield() { auto a = yield_coroutine(4); cout << "created a corutine, try to get a value\n"; do { cout << "get value " << a.current_value() << endl; } while (a.move_next()); }
librf是一个基于C++ Coroutines提案 ‘Stackless Resumable Functions’编写的非对称stackless协程库。
https://github.com/tearshark/librf
1.C++ 协程介绍[译]
2.C++协程
3.C++ coroutine
4.co_vs_callback
5.isocpp.org的experimental
6.万字长文 | 漫谈libco协程设计及实现
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。