概述
原文: https://en.cppreference.com/w/cpp/language/coroutines
Coroutines
A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack. This allows for sequential code that executes asynchronously (e.g. to handle non-blocking I/O without explicit callbacks), and also supports algorithms on lazy-computed infinite sequences and other uses.
A function is a coroutine if its definition does any of the following:
coroutine是一个可以被挂起和恢复的函数. 协程是无堆栈的:它们通过返回到调用者来暂停执行,恢复执行所需的数据与堆栈分开存储。这允许异步执行的顺序代码(例如,在没有显式回调的情况下处理非阻塞I/O),也支持延迟计算无限序列的算法和其他用途。
如果一个函数的定义有以下任何一种情况,那么它就是协程:
-
使用
co_await
操作符暂停执行,直到恢复task<> tcp_echo_server() { char data[1024]; for (;;) { size_t n = co_await socket.async_read_some(buffer(data)); co_await async_write(socket, buffer(data, n)); } }
-
使用关键字
co_yield
暂停执行,返回一个值generator<int> iota(int n = 0) { while(true) co_yield n++; }
-
使用关键字
co_return
完成执行,返回一个值lazy<int> f() { co_return 7; }
每个协程都必须有一个返回类型来满足以下的许多要求。
Restrictions
Coroutines cannot use variadic arguments, plain return statements, or placeholder return types (auto or Concept).Constexpr functions, constructors, destructors, and the main function cannot be coroutines.
限制条件:
协程不能使用可变参数( variadic arguments)、普通返回(return
)语句或占位符返回类型
(auto或Concept)。Constexpr函数、构造函数
、析构函数
和main函数
不能是协程。
执行
Execution
Each coroutine is associated with
- the
promise
object, manipulated from inside the coroutine. The coroutine submits its result or exception through this object.- the
coroutine handle
, manipulated from outside the coroutine. This is a non-owning handle used to resume execution of the coroutine or to destroy the coroutine frame.- the
coroutine state
, which is an internal, heap-allocated (unless the allocation is optimized out), object that contains
- the promise object
- the parameters (all copied by value)
- some representation of the current suspension point, so that resume knows where to continue and destroy knows what local variables were in scope
- local variables and temporaries whose lifetime spans the current suspension point
每个coroutine的关联对象:
promise
对象,从协程内部操纵。协程通过此对象提交其结果或异常。corotine handle
(协程句柄),从协程外部操纵。这是一个非所有者(non-owning)句柄,用于恢复协程的执行或销毁协程帧。coroutine state
(协程状态),它是一个内部的堆分配对象(除非分配被优化),包含:promise
对象- 参数(都是通过值拷贝)
- 当前挂起点的一些标记信息(representation),这样resume就知道在哪里继续,destroy就知道哪些局部变量在作用域中
- 生存期跨越当前挂起点的局部变量和临时变量
When a coroutine begins execution, it performs the following:
- allocates the coroutine state object using
operator new
(see below)- copies all function parameters to the coroutine state: by-value parameters are moved or copied, by-reference parameters remain references (and so may become dangling if the coroutine is resumed after the lifetime of referred object ends)
- calls the constructor for the promise object. If the promise type has a constructor that takes all coroutine parameters, that constructor is called, with post-copy coroutine arguments. Otherwise the default constructor is called.
- calls
promise.get_return_object()
and keeps the result in a local variable. The result of that call will be returned to the caller when the coroutine first suspends. Any exceptions thrown up to and including this step propagate back to the caller, not placed in the promise.- calls
promise.initial_suspend()
andco_awaits
its result. Typical Promise types either return a suspend_always, for lazily-started coroutines, or suspend_never, for eagerly-started coroutines.- when
co_await promise.initial_suspend()
resumes, starts executing the body of the coroutine
当协程开始执行时,它会执行以下操作:
- 使用
operator new
分配协程状态对象(见下文) - 将所有函数形参复制到协程状态:如果是按值传参则其被移动(move)或复制,如果是引用传参则保留引用(因此,如果在被引用对象的生命周期结束后恢复协程,可能会变得悬空, 因此, 程序员注意对象的生命周期)
- 调用promise对象的构造函数。如果promise类型有一个接受所有协程参数的构造函数,则调用该构造函数,并带有复制后的协程参数。否则,将调用默认构造函数。
- 调用
promise.get_return_object()
并将结果保存在一个局部变量中。当协程第一次挂起时,该调用的结果将返回给调用者。到此步骤为止抛出的任何异常(包括此步骤)都会传播回调用者,而不是放在promise中。 - 调用
promise.initial_suspend()
和co_await
其结果。典型的Promise类型要么为lazily-started(慢启动)协程返回一个suspend_always
,要么为eagerly-started(急启动)协程返回一个suspend_never
。 - 当
co_await promise.initial_suspend()
恢复时,开始执行协程体
When a coroutine reaches a suspension point
- the return object obtained earlier is returned to the caller/resumer, after implicit conversion to the return type of the coroutine, if necessary.
When a coroutine reaches the
co_return
statement, it performs the following:
- calls promise.return_void() for
co_return
;co_return expr
where expr has type void- falling off the end of a void-returning coroutine. The behavior is undefined if the Promise type has no
Promise::return_void()
member function in this case.- or calls
promise.return_value(expr)
forco_return
expr where expr has non-void type- destroys all variables with automatic storage duration in reverse order they were created.
- calls
promise.final_suspend()
andco_awaits
the result.If the coroutine ends with an uncaught exception, it performs the following:
- catches the exception and calls
promise.unhandled_exception()
from within the catch-block- calls
promise.final_suspend()
andco_awaits
the result (e.g. to resume a continuation or publish a result). It’s undefined behavior to resume a coroutine from this point.When the coroutine state is destroyed either because it terminated via
co_return
or uncaught exception, or because it was destroyed via its handle, it does the following:
- calls the destructor of the promise object.
- calls the destructors of the function parameter copies.
- calls
operator delete
to free the memory used by the coroutine state- transfers execution back to the caller/resumer.
当协程到达一个暂停点时
- 如果需要,在隐式转换为协程的返回类型之后,前面获得的返回对象返回给caller/resumer。
当协程到达co_return
语句时,它执行以下操作:
- 调用
promise.return_void()
co_return
;co_return expr
其中expr
是void
类型- 从返回空值的协程的末尾脱落。在这种情况下,如果Promise类型没有
Promise::return_void()
成员函数,则该行为是未定义(undefined的。
- 或者调用
promise.return_value(expr)
来获取co_return expr
,其中expr为非void类型 - 按创建时的相反顺序销毁所有自动变量。
- 调用
promise.final_suspend()
和co_await
结果。
如果协程以未捕获的异常结束,它将执行以下操作:
- 捕获异常并在catch块中调用
promise.unhandled_exception()
- 调用
promise.final_suspend()
和co_await
结果(例如恢复延续或发布结果)。从这一点恢复协程是未定义的行为。
当协程状态被销毁是因为它通过co_return
或未捕获的异常终止,或因为它是通过它的句柄销毁的,它会执行以下操作:
- 调用
promise
对象的析构函数。 - 调用函数参数副本的析构函数。
- 调用
operator delete
来释放协程状态所使用的内存 - 将执行传输回caller/resumer。
堆分配
Heap allocation
coroutine state is allocated on the heap via non-array operator new.If the Promise type defines a class-level replacement, it will be used, otherwise global operator new will be used.
If the Promise type defines a placement form of operator new that takes additional parameters, and they match an argument list where the first argument is the size requested (of type std::size_t) and the rest are the coroutine function arguments, those arguments will be passed to operator new (this makes it possible to use leading-allocator-convention for coroutines)
The call to operator new can be optimized out (even if custom allocator is used) if
- The lifetime of the coroutine state is strictly nested within the lifetime of the caller, and
- the size of coroutine frame is known at the call site
in that case, coroutine state is embedded in the caller’s stack frame (if the caller is an ordinary function) or coroutine state (if the caller is a coroutine)
If allocation fails, the coroutine throws std::bad_alloc, unless the Promise type defines the member function Promise::get_return_object_on_allocation_failure(). If that member function is defined, allocation uses the nothrow form of operator new and on allocation failure, the coroutine immediately returns the object obtained from Promise::get_return_object_on_allocation_failure() to the caller.
协程状态是通过非数组操作符new在堆上分配的。
如果Promise类型定义了类级别的operator new
,则使用它,否则将使用全局 operator new
。
如果Promise类型定义了一个需要额外的参数的operator new
作为替代,和他们匹配一个参数列表,第一个参数是请求的大小(类型的std:: size_t),其余是协同程序函数参数,这些参数将传递给operator new
的(这使它可以使用leading-allocator-convention协程)
对operator new的调用可以优化出来(即使使用了自定义分配器),如果:
- 协程状态的生存期严格嵌套在调用者的生存期内,并且
- 协程帧的大小在调用站点是已知的
在这种情况下,协程状态被嵌入到调用者的堆栈框架中(如果调用者是一个普通函数)或协程状态(如果调用者是一个协程)
如果分配失败,则该coroutine将抛出std::bad_alloc
,除非Promise类型定义了成员函数Promise::get_return_object_on_allocation_failure()
。如果定义了该成员函数,则allocation使用operator new
的nothrow
形式,并且在分配失败时,协程立即将Promise::get_return_object_on_allocation_failure()
获得的对象返回给调用者。
Promise
The Promise type is determined by the compiler from the return type of the coroutine using
std::coroutine_traits
.
Formally, let R and Args… denote the return type and parameter type list of a coroutine respectively, ClassT and /cv-qual/ (if any) denote the class type to which the coroutine belongs and its cv-qualification respectively if it is defined as a non-static member function, its Promise type is determined by:
std::coroutine_traits<R, Args...>::promise_type
, if the coroutine is not defined as a non-static member function,std::coroutine_traits<R, ClassT /*cv-qual*/&, Args...>::promise_type
, if the coroutine is defined as a non-static member function that is not rvalue-reference-qualified,std::coroutine_traits<R, ClassT /*cv-qual*/&&, Args...>::promise_type
, if the coroutine is defined as a non-static member function that is rvalue-reference-qualified.
For example:
- If the coroutine is defined as
task<float> foo(std::string x, bool flag);
, then its Promise type isstd::coroutine_traits<task<float>, std::string, bool>::promise_type
.- If the coroutine is defined as
task<void> my_class::method1(int x) const
;, its Promise type isstd::coroutine_traits<task<void>, const my_class&, int>::promise_type
.- If the coroutine is defined as
task<void> my_class::method1(int x) &&;
, its Promise type isstd::coroutine_traits<task<void>, my_class&&, int>::promise_type
.
Promise类型由编译器根据使用std::coroutine_traits
的协程返回类型确定。正式地,设R
和Args…分别表示协程的返回类型和参数类型列表,
classsT`和/cv-qual/(如果有的话)分别表示协程所属的类类型和它的cv限定条件。如果它被定义为一个非静态成员函数,它的Promise类型由:
std::coroutine_traits<R, Args...>::promise_type
,如果协程未定义为非静态成员函数,std::coroutine_traits<R, ClassT /*cv-qual*/&, Args...>::promise_type
, 如果协程定义为非rvalue-reference限定的非静态成员函数,std::coroutine_traits<R, ClassT /*cv-qual*/&&, Args...>::promise_type
, 如果协程定义为rvalue-reference限定的非静态成员函数。
举例:
- 如果coroutine被定义为
task<float> foo(std::string x, bool flag);
, 那么它的Promise类型是std::coroutine_traits<task<float>, std::string, bool>::promise_type
. - 如果coroutine被定义为
task<void> my_class::method1(int x) const
;, 那么它的Promise类型是std::coroutine_traits<task<void>, const my_class&, int>::promise_type
. - 如果coroutine被定义为
task<void> my_class::method1(int x) &&;
, 那么它的Promise类型是std::coroutine_traits<task<void>, my_class&&, int>::promise_type
.
co_await
The unary operator co_await suspends a coroutine and returns control to the caller. Its operand is an expression whose type must either define operator co_await, or be convertible to such type by means of the current coroutine’s
Promise::await_transform
一元操作符co_await
挂起协程并将控制权返回给调用者。它的操作数是一个表达式,其类型必须定义操作符co_await
,或者通过当前协程的Promise::await_transform
可转换为该类型
co_await expr
First, expr is converted to an awaitable as follows:
- if expr is produced by an initial suspend point, a final suspend point, or a yield expression, the awaitable is expr, as-is.
- otherwise, if the current coroutine’s Promise type has the member function await_transform, then the awaitable is promise.await_transform(expr)
- otherwise, the awaitable is expr, as-is.
首先,expr被转换为可等待对象,如下所示:
- 如果expr是由初始挂起点、最终挂起点或yield表达式生成的,则可等待对象按实际情况为expr。
- 否则,如果当前协程的Promise类型有成员函数await_transform,那么可等待对象就是Promise .await_transform(expr)
- 否则,可等待对象就是expr。
If the expression above is a prvalue, the awaiter object is a temporary materialized from it. Otherwise, if the expression above is an glvalue, the awaiter object is the object to which it refers.
如果上面的表达式是prvalue,则awaiter对象是它的临时实体化对象。否则,如果上面的表达式是glvalue,则awaiter对象就是它所引用的对象。
Then, awaiter.await_ready() is called (this is a short-cut to avoid the cost of suspension if it’s known that the result is ready or can be completed synchronously). If its result, contextually-converted to bool is false then
然后,调用await .await_ready()
如果知道结果已经就绪或可以同步完成,这是一种避免挂起代价的捷径)。如果它的结果,上下文转换为bool则为false
The coroutine is suspended (its coroutine state is populated with local variables and current suspension point).
awaiter.await_suspend(handle) is called, where handle is the coroutine handle representing the current coroutine. Inside that function, the suspended coroutine state is observable via that handle, and it’s this function’s responsibility to schedule it to resume on some executor, or to be destroyed (returning false counts as scheduling)
协程被挂起(它的协程状态由局部变量和当前挂起点填充)。调用await .await_suspend(句柄),其中句柄是表示当前协程的协程句柄。在这个函数内部,挂起的协程状态是可以通过这个句柄观察到的,这个函数的责任是安排它在某些执行器上恢复,或被销毁(返回错误计数作为调度)。
- if await_suspend returns void, control is immediately returned to the caller/resumer of the current coroutine (this coroutine remains suspended), otherwise
- if await_suspend returns bool,
- the value true returns control to the caller/resumer of the current coroutine
- the value false resumes the current coroutine.
- if await_suspend returns a coroutine handle for some other coroutine, that handle is resumed (by a call to handle.resume()) (note this may chain to eventually cause the current coroutine to resume)
- if await_suspend throws an exception, the exception is caught, the coroutine is resumed, and the exception is immediately re-thrown
-
如果await_suspend返回void,则控制权立即返回给当前协程的调用者/恢复者(该协程保持挂起状态),否则
- 如果await_suspend返回bool值,
- 值true将控制权返回给当前协程的调用者/恢复者
- 如果值为false,则恢复当前协程。
-
如果await_suspend返回其他协程的协程句柄,该句柄将被恢复(通过调用handle.resume())(注意这可能导致当前协程最终恢复)
-
如果await_suspend抛出异常,异常被捕获,协程被恢复,异常立即被重新抛出
Finally, awaiter.await_resume() is called, and its result is the result of the whole
co_await expr
expression.
If the coroutine was suspended in the co_await expression, and is later resumed, the resume point is immediately before the call to awaiter.await_resume().
最后,调用await .await_resume()
,其结果是整个co_await expr
表达式的结果。
如果协程在co_await
表达式中被挂起,然后被恢复,恢复点就在调用await .await_resume()
之前。
Note that because the coroutine is fully suspended before entering awaiter.await_suspend(), that function is free to transfer the coroutine handle across threads, with no additional synchronization. For example, it can put it inside a callback, scheduled to run on a threadpool when async I/O operation completes. In that case, since the current coroutine may have been resumed and thus executed the awaiter object’s destructor, all concurrently as
await_suspend()
continues its execution on the current thread,await_suspend()
should treat*this
as destroyed and not access it after the handle was published to other threads.
注意,因为协程在进入await .await_suspend()
之前已经完全挂起,所以该函数可以自由地跨线程传递协程句柄,而不需要额外的同步操作。例如,它可以将其放在回调函数中,计划在异步I/O操作完成时在线程池中运行。在这种情况下,因为当前的协同程序可能已经恢复,因此等待对象的析构函数执行,所有并发await_suspend()
在当前线程继续执行, await_suspend()
应该把*this
当作已经销毁并且在将句柄发布到其他线程之后不要再去访问它。
例子
#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
auto switch_to_new_thread(std::jthread& out) {
struct awaitable {
std::jthread* p_out;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::jthread& out = *p_out;
if (out.joinable())
throw std::runtime_error("Output jthread parameter not empty");
out = std::jthread([h] { h.resume(); });
// Potential undefined behavior: accessing potentially destroyed *this
// std::cout << "New thread ID: " << p_out->get_id() << 'n';
std::cout << "New thread ID: " << out.get_id() << 'n'; // this is OK
}
void await_resume() {}
};
return awaitable{&out};
}
struct task{
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
task resuming_on_new_thread(std::jthread& out) {
std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << 'n';
co_await switch_to_new_thread(out);
// awaiter destroyed here
std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << 'n';
}
int main() {
std::jthread out;
resuming_on_new_thread(out);
}
需要使用gcc 10.2
进行编译
$ g++ --version
g++ (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0
$ g++ coroutine.cc -std=c++20 -fcoroutines
$ ./a.out
Coroutine started on thread: 140421255046976
New thread ID: 140421255042816
Coroutine resumed on thread: 140421255042816
This section is incomplete
Reason: examples
这一部分尚未完成
co_yield
co_yield
Yield-expression returns a value to the caller and suspends the current coroutine: it is the common building block of resumable generator functions
co_yield expr
co_yield braced-init-list
It is equivalent to
co_await promise.yield_value(expr)
A typical generator’s yield_value would store (copy/move or just store the address of, since the argument’s lifetime crosses the suspension point inside the co_await) its argument into the generator object and return std::suspend_always, transferring control to the caller/resumer.
Yield-expression返回一个值给调用者,并挂起当前协程:它可以构建可恢复生成器函数(类似python中的yield
)
co_yield expr
co_yield braced-init-list
它等价于
co_await promise.yield_value(expr)
一个典型的生成器的yield_value
将其参数存储(复制/移动或仅仅存储其地址,因为参数的生命周期跨越了co_await
内部的悬挂点)到生成器对象中,并返回std:: susend_always
,将控制权转移给caller/resumer。
This section is incomplete
Reason: examples
这一部分尚未完成
最后
以上就是舒服蜜粉为你收集整理的C++20-协程(coroutine)的全部内容,希望文章能够帮你解决C++20-协程(coroutine)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复