新闻发布的网站,网站内容的作用,top网站怎么做,wordpress怎么加动态背景图图片C20 协程参考手册详解 - 源自 cppreference.com
人话版
先说“人说”#xff0c;简化版本#xff0c;更易理解。
宏观概念#xff1a;协程是一个可以暂定和恢复执行的函数。#xff08;普通函数是线程相关的#xff0c;函数的调用依赖于线程栈#xff0c;而协程的运行…C20 协程参考手册详解 - 源自 cppreference.com
人话版
先说“人说”简化版本更易理解。
宏观概念协程是一个可以暂定和恢复执行的函数。普通函数是线程相关的函数的调用依赖于线程栈而协程的运行状态暂定状态保存在堆区协程在线程A暂停后后续可以在另一个线程B继续上次的状态执行。
具体的在C中只要函数出现了co_awaitco_yield或co_return这三个关键字之一函数就由编译器变为协程。
co_await
co_await的作用是让协程“暂停一下”等待某个操作(比如网络请求或文件读取)完成后再继续执行。co_await就是这个“等一等”的动作暂停协程干别的事等条件满足再回来。
但问题来了如果你直接对一个自定义类型用co_await,比如co_await IntReader{}编译器会一脸懵逼。它不知道这个类型啥时候算“完成”也不知道结果在哪儿。为了让co_await能用我们需要让自定义类型遵守一个规则这个规则叫 Awaitable。
Awaitable就像一份“协程使用说明书”告诉编译器怎么处理暂停和恢复。它要求你的类型实现三个关键函数:
bool await_ready()告诉协程“现在能不能直接执行?” 返回类型bool。作用在执行co_awit时先执行这个函数检测是否可以立即执行避免不必要的暂停。 void await_suspend(std::coroutine_handle h)如果要暂停接下来该干啥? 返回类型可以是void也可以是boolture表暂停false表不暂停。参数coroutine_handle 协程的“遥控器”。本质上std::coroutine_handle 是一个指向堆上分配的协程帧coroutine frame的、轻量级的指针。std::coroutine_handle (通用遥控器)它可以指向任何类型的协程无论其 promise_type 是什么因为它不需要关心挂起的是哪种协程它只需要一个通用的句柄以便之后能调用 .resume() 即可。coroutine_handlePromiseType (专用遥控器)因为编译器知道 Promise 的具体类型所以你可以安全地调用 .promise() 方法获得对 PromiseType 对象的引用然后从中取出结果。作用如果协程要暂停会调用await_suspend()通过参数拿到协程句柄指向当前暂定的协程实例可以在未来某个时刻“唤醒”协程h.resume())。 void await_resume()恢复时返回什么结果? 返回类型可以是void也可以是具体类型作用当协程恢复执行时或压根没暂停被调用其返回值就是co_awit表达式的结果。 这三个函数一起合作让co_await知道如何暂停、等待和继续
initial_suspend和final_suspend相当于协程生命周期开始和结束时的两个“守门人”它们通过返回一个 Awaitable 对象通常是 std::suspend_always 或 std::suspend_never来决定协程在两个关键时刻的行为
initial_suspend执行您编写的任何一行协程体代码之前initial_suspend 会被自动调用并 co_await它回答了一个关键问题“协程被调用后****是应该立刻开始执行还是应该创建一个‘暂停’的任务等待调用者明确命令才开始”final_suspend当协程的函数体执行完毕无论是通过 co_return 正常结束还是执行到函数末尾隐式结束并且所有局部变量都已被析构之后final_suspend 会被自动调用并 co_await。它回答了另一个关键问题“协程执行完毕后是应该立即自我销毁还是应该停留在‘已完成’的状态等待外界来处理它的‘后事’比如读取返回值”
#include iostream
#include coroutine
#include thread
#include unistd.hclass IntReader{
public:bool await_ready() { return false;}void await_suspend(std::coroutine_handle handle){std::thread thread([this,handle](){sleep(1);value_ 1;handle.resume();});thread.detach();}int await_resume(){return value_;}
private:int value_;};// Task这里先不用管只需要关注上面的IntReader类
class Task;
class Task{
public:class promise_type{public:Task get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void unhandled_exception() {}void return_void() {}};
};Task PrintInt(){IntReader reader1;int total co_await reader1;IntReader reader2;total co_await reader2;IntReader reader3;total co_await reader3;std::couttotalstd::endl;
}
int main(){PrintInt();getchar();return 0;
}co_return
协程的返回类型要求
C对协程的返回类型只有一个硬性规定它必须包含一个名为promise_type的内嵌类型
当你调用一个协程函数时:
编译器会在堆上分配空间保存协程的状态同时创建一个promise_type对象嵌在返回类型里通过promise_type定义的函数你可以控制协程的行为或者与调用者交换数据。
promise_type的核心函数:get_return_object()
promise_type必须实现一个函数:get_return_object()。它的作用是创建协程的返回值对象。
调用时机:
在协程函数被调用时编译器会先创建promise_typer对象然后调用get_return_object()生成返回类型(比如Task)给调用者。promise_type是返回类型的内嵌类型但编译器不会直接构造返回类型而是通过promise_type来“间接”生成它。
返回值的作用
取决于设计者的意图。如果只是想让协程干活比如打印返回值可以是个空壳。如果想让协程返回数据 就要在返回类型里设计获取数据的接口。
#include iostream
#include coroutine
#include memory
#include thread
#include unistd.hclass IntReader{
public:bool await_ready() { return false;}void await_suspend(std::coroutine_handle handle){std::thread thread([this,handle](){sleep(1);value_ 1;handle.resume();});thread.detach();}int await_resume(){return value_;}
private:int value_;};class Task;
class Task{
public:class promise_type{public:promise_type(): value_(std::make_sharedint()) {}Task get_return_object() { return Task{value_}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void unhandled_exception() {}void return_value(int value){*value_value;}private:std::shared_ptrint value_;};Task(const std::shared_ptrint value): value_(value) {}int GetValue() const{return *value_;}}
private:std::shared_ptrint value_;};Task GetInt(){IntReader reader1;int total co_await reader1;IntReader reader2;total co_await reader2;IntReader reader3;total co_await reader3;co_return total;
}
int main(){auto task GetInt();sleep(4); // 这里为了方便直接等4秒std::couttask.GetValue()std::endl;getchar();return 0;
}co_yield
co_yield 表达式向调用者返回一个值并暂停当前协程它是可恢复生成器 (generator) 函数的常见构建块。
co_yield expr
co_yield braced-init-list它等价于
co_await promise.yield_value(expr)一个典型的生成器的 yield_value 会将其参数存储复制/移动或仅存储地址因为参数的生命周期在 co_await 内部跨越了暂停点到生成器对象中并返回 std::suspend_always将控制权转移给调用者/恢复者。
示例斐波那契生成器
#include coroutine
#include cstdint
#include exception
#include iostreamtemplatetypename T
struct Generator {// 类名 Generator 是我们的选择对于协程魔法来说不是必需的。// 编译器通过 co_yield 关键字的存在来识别协程。// 你可以使用 MyGenerator (或任何其他名字)只要你包含// 一个带有 MyGenerator get_return_object() 方法的嵌套结构体 promise_type。// (注意重命名时需要调整构造函数和析构函数的声明。)struct promise_type;using handle_type std::coroutine_handlepromise_type;struct promise_type // 必需{T value_;std::exception_ptr exception_;Generator get_return_object() {return Generator(handle_type::from_promise(*this));}std::suspend_always initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }void unhandled_exception() { exception_ std::current_exception(); } // 保存异常templatestd::convertible_toT From // C20 conceptstd::suspend_always yield_value(From from) {value_ std::forwardFrom(from); // 在 promise 中缓存结果return {};}void return_void() {}};handle_type h_;Generator(handle_type h) : h_(h) {}~Generator() { h_.destroy(); }explicit operator bool() {fill(); // 要可靠地判断协程是否结束// 以及是否将通过C的getter(下方的operator())在协程中生成下一个值(co_yield)// 唯一的方法是执行/恢复协程直到下一个co_yield点或让它执行完毕。// 然后我们将结果存储/缓存在 promise 中以允许 getter (下方的operator())// 在不执行协程的情况下获取它。return !h_.done();}T operator()() {fill();full_ false; // 我们将要移出先前缓存的结果使 promise 再次变空return std::move(h_.promise().value_);}private:bool full_ false;void fill() {if (!full_) {h_();if (h_.promise().exception_)std::rethrow_exception(h_.promise().exception_);// 在调用上下文中传播协程异常full_ true;}}
};Generatorstd::uint64_t
fibonacci_sequence(unsigned n) {if (n 0)co_return;if (n 94)throw std::runtime_error(斐波那契序列太大元素会溢出。);co_yield 0;if (n 1)co_return;co_yield 1;if (n 2)co_return;std::uint64_t a 0;std::uint64_t b 1;for (unsigned i 2; i n; i) {std::uint64_t s a b;co_yield s;a b;b s;}
}int main() {try {auto gen fibonacci_sequence(10); // 在 uint64_t 溢出前最大为 94for (int j 0; gen; j)std::cout fib( j ) gen() \n;}catch (const std::exception ex) {std::cerr 异常: ex.what() \n;}catch (...) {std::cerr 未知异常。\n;}
}执行流程
每个协程都与以下三者相关联
Promise 对象 (the promise object)在协程内部被操纵。协程通过此对象提交其结果或异常。Promise 对象与 std::promise 没有任何关系。协程句柄 (the coroutine handle)在协程外部被操纵。这是一个**非拥有式non-owning**的句柄用于恢复协程的执行或销毁协程帧。协程状态 (the coroutine state)这是一个内部的、动态分配除非分配被优化掉的对象它包含 Promise 对象。所有参数均按值复制。当前暂停点的某种表示以便恢复时知道从何处继续销毁时知道哪些局部变量在作用域内。生命周期跨越当前暂停点的局部变量和临时对象。
当一个协程开始执行时它会执行以下操作
使用 operator new 分配协程状态对象。将所有函数参数复制到协程状态中按值传递的参数被移动或复制按引用传递的参数仍然是引用因此如果协程在被引用对象的生命周期结束后恢复可能会导致悬挂引用——见下文示例。调用 Promise 对象的构造函数。如果 Promise 类型有一个接受所有协程参数的构造函数则会使用该构造函数并传入复制后的协程参数。否则调用默认构造函数。调用 promise.get_return_object() 并将结果保存在一个局部变量中。该调用的结果将在协程首次暂停时返回给调用者。在此步骤及之前抛出的任何异常都会传播回调用者而不会被放入 Promise 中。调用 promise.initial_suspend() 并 co_await 其结果。典型的 Promise 类型会返回 std::suspend_always用于懒启动的协程或 std::suspend_never用于急切启动的协程。当 co_await promise.initial_suspend() 恢复时开始执行协程的主体部分。
当协程到达一个暂停点时
先前获得的返回对象会被返回给调用者/恢复者如有必要会进行到协程返回类型的隐式转换。
当协程到达 co_return 语句时它会执行以下操作
对于 co_return; 或 co_return expr;其中 expr 类型为 void调用 promise.return_void()。对于 co_return expr;其中 expr 类型非 void调用 promise.return_value(expr)。按创建顺序的逆序销毁所有具有自动存储期的变量。调用 promise.final_suspend() 并 co_await 其结果。
如果协程执行完函数体末尾而没有 co_return这等价于 co_return;但如果 Promise 域中找不到 return_void 的声明则行为是未定义的。一个在其函数体内没有任何协程定义关键字的函数不是协程无论其返回类型如何如果其返回类型不是 void可有 cv 限定则执行到函数末尾会导致未定义行为。
如果协程因未捕获的异常而结束它会执行以下操作
捕获异常并在 catch 块内调用 promise.unhandled_exception()。调用 promise.final_suspend() 并 co_await 其结果例如用于恢复一个延续或发布一个结果。从此时恢复协程是未定义行为。
当协程状态被销毁时无论是通过 co_return、未捕获的异常终止还是通过其句柄销毁它会执行以下操作
调用 Promise 对象的析构函数。调用函数参数副本的析构函数。调用 operator delete 来释放协程状态所使用的内存。将执行权交还给调用者/恢复者。
以上“人话版”是看完视频后的总结于扩展。 官方手册
引言
本篇内容是 en.cppreference.com Coroutines (C20) 页面的完整中文翻译旨在为需要精确参考的 C 开发者提供一份详尽的中文对应文档。 协程 (C20)
协程是一种可以暂停执行以便后续恢复的函数。协程是无栈的stackless它们通过返回至调用者来暂停执行而恢复执行所需的数据与栈分离存储。这使得顺序执行的代码可以异步执行例如无需显式回调即可处理非阻塞 I/O同时也支持对惰性计算的无限序列进行算法操作以及其他用途。
如果一个函数的定义包含了以下任何一种情况那么它就是一个协程 co_await 表达式 — 暂停执行直到被恢复。 task tcp_echo_server() {char data[1024];while (true){std::size_t n co_await socket.async_read_some(buffer(data));co_await async_write(socket, buffer(data, n));}
}co_yield 表达式 — 暂停执行并返回一个值。 generatorunsigned int iota(unsigned int n 0) {while (true)co_yield n;
}co_return 语句 — 完成执行并返回一个值。 lazyint f() {co_return 7;
}每个协程都必须有一个满足下述多项要求的返回类型。 限制 (Restrictions)
协程不能使用可变参数variadic arguments、普通的 return 语句或占位符返回类型auto 或 Concept。consteval 函数、constexpr 函数、构造函数、析构函数以及 main 函数不能是协程。 执行 (Execution)
每个协程都与以下三者相关联
Promise 对象 (the promise object)在协程内部被操纵。协程通过此对象提交其结果或异常。Promise 对象与 std::promise 没有任何关系。协程句柄 (the coroutine handle)在协程外部被操纵。这是一个**非拥有式non-owning**的句柄用于恢复协程的执行或销毁协程帧。协程状态 (the coroutine state)这是一个内部的、动态分配除非分配被优化掉的对象它包含 Promise 对象。所有参数均按值复制。当前暂停点的某种表示以便恢复时知道从何处继续销毁时知道哪些局部变量在作用域内。生命周期跨越当前暂停点的局部变量和临时对象。
当一个协程开始执行时它会执行以下操作
使用 operator new 分配协程状态对象。将所有函数参数复制到协程状态中按值传递的参数被移动或复制按引用传递的参数仍然是引用因此如果协程在被引用对象的生命周期结束后恢复可能会导致悬挂引用——见下文示例。调用 Promise 对象的构造函数。如果 Promise 类型有一个接受所有协程参数的构造函数则会使用该构造函数并传入复制后的协程参数。否则调用默认构造函数。调用 promise.get_return_object() 并将结果保存在一个局部变量中。该调用的结果将在协程首次暂停时返回给调用者。在此步骤及之前抛出的任何异常都会传播回调用者而不会被放入 Promise 中。调用 promise.initial_suspend() 并 co_await 其结果。典型的 Promise 类型会返回 std::suspend_always用于懒启动的协程或 std::suspend_never用于急切启动的协程。当 co_await promise.initial_suspend() 恢复时开始执行协程的主体部分。
一些参数变为悬挂引用的示例
#include coroutine
#include iostreamstruct promise;struct coroutine : std::coroutine_handlepromise {using promise_type ::promise;
};struct promise {coroutine get_return_object() { return {coroutine::from_promise(*this)}; }std::suspend_always initial_suspend() noexcept { return {}; }std::suspend_always final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() {}
};struct S {int i;coroutine f() {std::cout i;co_return;}
};void bad1() {coroutine h S{0}.f();// S{0} 已销毁h.resume(); // 恢复的协程执行 std::cout i在 S::i 释放后使用h.destroy();
}coroutine bad2() {S s{0};return s.f(); // 返回的协程无法在不产生“使用已释放内存”的情况下被恢复
}void bad3() {coroutine h [i 0]() - coroutine // 一个同时也是协程的 lambda{std::cout i;co_return;}(); // 立即调用// lambda 已销毁h.resume(); // 在 (匿名 lambda 类型)::i 释放后使用h.destroy();
}void good() {coroutine h [](int i) - coroutine // 将 i 设为协程参数{std::cout i;co_return;}(0);// lambda 已销毁h.resume(); // 没有问题i 作为一个按值传递的参数已被复制到协程帧中h.destroy();
}当协程到达一个暂停点时
先前获得的返回对象会被返回给调用者/恢复者如有必要会进行到协程返回类型的隐式转换。
当协程到达 co_return 语句时它会执行以下操作
对于 co_return; 或 co_return expr;其中 expr 类型为 void调用 promise.return_void()。对于 co_return expr;其中 expr 类型非 void调用 promise.return_value(expr)。按创建顺序的逆序销毁所有具有自动存储期的变量。调用 promise.final_suspend() 并 co_await 其结果。
如果协程执行完函数体末尾而没有 co_return这等价于 co_return;但如果 Promise 域中找不到 return_void 的声明则行为是未定义的。一个在其函数体内没有任何协程定义关键字的函数不是协程无论其返回类型如何如果其返回类型不是 void可有 cv 限定则执行到函数末尾会导致未定义行为。
// 假设 task 是某种协程任务类型
taskvoid f() {// 不是协程未定义行为
}taskvoid g() {co_return; // OK
}taskvoid h() {co_await g();// OK隐式的 co_return;
}如果协程因未捕获的异常而结束它会执行以下操作
捕获异常并在 catch 块内调用 promise.unhandled_exception()。调用 promise.final_suspend() 并 co_await 其结果例如用于恢复一个延续或发布一个结果。从此时恢复协程是未定义行为。
当协程状态被销毁时无论是通过 co_return、未捕获的异常终止还是通过其句柄销毁它会执行以下操作
调用 Promise 对象的析构函数。调用函数参数副本的析构函数。调用 operator delete 来释放协程状态所使用的内存。将执行权交还给调用者/恢复者。 动态分配 (Dynamic allocation)
协程状态通过非数组形式的 operator new 进行动态分配。 如果 Promise 类型定义了类级别的替换则会使用它否则使用全局的 operator new。 如果 Promise 类型定义了接受额外参数的放置式 operator new并且这些参数与一个参数列表匹配第一个参数是请求的大小 std::size_t其余是协程函数的参数那么这些参数将被传递给 operator new这使得对协程使用前置分配器约定 (leading-allocator-convention) 成为可能。 如果以下条件满足对 operator new 的调用可以被优化掉即使使用了自定义分配器 协程状态的生命周期严格嵌套在调用者的生命周期内并且协程帧的大小在调用点已知。 在这种情况下协程状态被嵌入在调用者的栈帧如果调用者是普通函数或协程状态如果调用者是协程中。 如果分配失败协程会抛出 std::bad_alloc除非Promise 类型定义了成员函数Promise::get_return_object_on_allocation_failure()。如果定义了该函数分配将使用 nothrow 形式的 operator new并且在分配失败时协程会立即将从 Promise::get_return_object_on_allocation_failure() 获得的对象返回给调用者。例如 struct Coroutine::promise_type {/* ... */// 确保使用不会抛出异常的 operator-newstatic Coroutine get_return_object_on_allocation_failure() {std::cerr __func__ \n;throw std::bad_alloc(); // 或者, return Coroutine(nullptr);}// 自定义的非抛出异常的 new 重载void* operator new(std::size_t n) noexcept {if (void* mem std::malloc(n))return mem;return nullptr; // 分配失败}
};Promise
Promise 类型由编译器根据协程的返回类型使用 std::coroutine_traits 来确定。
正式地说令 R 和 Args... 分别表示协程的返回类型和参数类型列表ClassT 表示协程所属的类类型如果它被定义为非静态成员函数cv 表示其 cv 限定符其 Promise 类型由以下方式确定
std::coroutine_traitsR, Args...::promise_type如果协程不是隐式对象成员函数。std::coroutine_traitsR, cv ClassT, Args...::promise_type如果协程是左值引用限定的隐式对象成员函数。std::coroutine_traitsR, cv ClassT, Args...::promise_type如果协程是右值引用限定的隐式对象成员函数。
例如
如果协程定义为…那么其 Promise 类型是…taskvoid foo(int x);std::coroutine_traitstaskvoid, int::promise_typetaskvoid Bar::foo(int x) const;std::coroutine_traitstaskvoid, const Bar, int::promise_typetaskvoid Bar::foo(int x) ;std::coroutine_traitstaskvoid, Bar, int::promise_type好的这是您指定的 cppreference.com 协程页面后续内容的完整、忠实翻译。 co_await
一元运算符 co_await 暂停协程并将控制权返回给调用者。
co_await exprco_await 表达式只能出现在常规函数体包括 lambda 表达式的函数体内的潜在求值表达式 (potentially-evaluated expression) 中且不能出现在
handler 中。declaration 语句中除非它出现在该声明的初始化器中。init-statement 的 simple-declaration 中参见 if、switch、for 和 range-for除非它出现在该 init-statement 的初始化器中。默认参数中。具有静态或线程存储期的块作用域变量的初始化器中。(C26 起) co_await 表达式不能是契约断言 (contract assertion) 谓词的潜在求值子表达式。
首先expr 被转换为一个可等待对象 (awaitable)如下所示
如果 expr 来自初始暂停点、最终暂停点或 yield 表达式则 awaitable 就是 expr 本身。否则如果当前协程的 Promise 类型有成员函数 await_transform则 awaitable 是 promise.await_transform(expr) 的结果。否则awaitable 就是 expr 本身。
然后获得 awaiter 对象如下所示
如果 operator co_await 的重载决议给出了唯一的最佳重载则 awaiter 是该调用的结果 对于成员重载awaitable.operator co_await()对于非成员重载operator co_await(static_castAwaitable(awaitable)) 否则如果没有找到 operator co_await则 awaiter 就是 awaitable 本身。否则如果重载决议有歧义则程序是病态的 (ill-formed)。
如果上述表达式是一个右值prvalue则 awaiter 对象是从它物化materialized出的一个临时对象。否则如果表达式是一个泛左值glvalue则 awaiter 对象是它所引用的对象。
接着调用 awaiter.await_ready() 这是一个快捷方式用于在已知结果已就绪或可同步完成时避免挂起的开销。如果其结果在上下文中转换为 bool 后为 false则
协程被暂停其协程状态被填充了局部变量和当前暂停点。调用 awaiter.await_suspend(handle)其中 handle 是表示当前协程的句柄。在此函数内部被挂起的协程状态可通过该句柄观察到并且此函数有责任调度它在某个执行器上恢复或被销毁返回 false 算作一种调度。 如果 await_suspend 返回 void控制权立即返回给当前协程的调用者/恢复者此协程保持挂起状态。否则如果 await_suspend 返回 bool 值 true 将控制权返回给当前协程的调用者/恢复者。值 false 则恢复当前协程的执行。 如果 await_suspend 返回某个其他协程的句柄则该句柄被恢复通过调用 handle.resume()注意这可能形成调用链并最终导致当前协程被恢复。如果 await_suspend 抛出异常异常被捕获协程被恢复然后异常被立即重新抛出。
最后awaiter.await_resume() 被调用无论协程是否被挂起其结果就是整个 co_await expr 表达式的结果。
如果协程在 co_await 表达式中被挂起并在稍后被恢复则恢复点位于调用 awaiter.await_resume() 之前。
注意协程在进入 awaiter.await_suspend() 之前已完全挂起。它的句柄可以被共享给另一个线程并在 await_suspend() 函数返回之前被恢复。注意默认的内存安全规则仍然适用因此如果协程句柄在没有锁的情况下跨线程共享awaiter 应该至少使用释放语义 (release semantics)而恢复者应该至少使用获取语义 (acquire semantics)。例如协程句柄可以放入一个回调中在异步 I/O 操作完成时调度在线程池上运行。在这种情况下由于当前协程可能已经被恢复并因此执行了 awaiter 对象的析构函数而这一切与 await_suspend() 在当前线程上继续执行是并发的所以 await_suspend() 在将句柄发布给其他线程后应将 *this 视为已销毁不再访问它。
示例
#include coroutine
#include iostream
#include stdexcept
#include threadauto 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(); });// 潜在的未定义行为: 访问可能已被销毁的 *this// std::cout New thread ID: p_out-get_id() \n;std::cout New thread ID: out.get_id() \n; // 这样是 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 在此处被销毁std::cout Coroutine resumed on thread: std::this_thread::get_id() \n;
}int main() {std::jthread out;resuming_on_new_thread(out);
}可能输出:
Coroutine started on thread: 139972277602112
New thread ID: 139972267284224
Coroutine resumed on thread: 139972267284224注意: awaiter 对象是协程状态的一部分作为一个生命周期跨越暂停点的临时对象并且在 co_await 表达式结束前被销毁。它可以用于维护某些异步 I/O API 所需的每次操作的状态而无需额外的动态分配。
标准库定义了两个平凡的 awaitablestd::suspend_always 和 std::suspend_never。 好的遵照您的指示这是对您提供的剩余部分内容的完整、忠实的中文翻译和博客形式的重写。 示例promise_type::await_transform 和程序提供的 Awaiter
这个例子演示了 promise_type 如何通过 await_transform 成员函数来“拦截”并转换一个 co_await 表达式返回一个自定义的 awaiter从而实现对协程暂停行为的动态控制。
#include cassert
#include coroutine
#include iostreamstruct tunable_coro {// 一个 Awaiter其“就绪”状态由构造函数的参数决定。class tunable_awaiter {bool ready_;public:explicit(false) tunable_awaiter(bool ready) : ready_{ready} {}// 三个标准的 awaiter 接口函数bool await_ready() const noexcept { return ready_; }static void await_suspend(std::coroutine_handle) noexcept {}static void await_resume() noexcept {}};struct promise_type {using coro_handle std::coroutine_handlepromise_type;auto get_return_object() { return coro_handle::from_promise(*this); }static auto initial_suspend() { return std::suspend_always(); }static auto final_suspend() noexcept { return std::suspend_always(); }static void return_void() {}static void unhandled_exception() { std::terminate(); }// 一个用户提供的转换函数它返回自定义的 awaiterauto await_transform(std::suspend_always) { return tunable_awaiter(!ready_); }void disable_suspension() { ready_ false; }private:bool ready_{true};};tunable_coro(promise_type::coro_handle h) : handle_(h) { assert(h); }// 为简化起见将这4个特殊成员函数声明为 deletedtunable_coro(tunable_coro const) delete;tunable_coro(tunable_coro) delete;tunable_coro operator(tunable_coro const) delete;tunable_coro operator(tunable_coro) delete;~tunable_coro() {if (handle_)handle_.destroy();}void disable_suspension() const {if (handle_.done())return;handle_.promise().disable_suspension();handle_();}bool operator()() {if (!handle_.done())handle_();return !handle_.done();}
private:promise_type::coro_handle handle_;
};tunable_coro generate(int n) {for (int i{}; i ! n; i) {std::cout i ;// 传递给 co_await 的 awaiter 会进入 promise_type::await_transform// 它会发出一个 tunable_awaiter这个 awaiter 最初会导致挂起在每次迭代时返回到 main// 但在调用 disable_suspension 之后就不会再发生挂起// 循环会一直运行到结束而不会返回到 main()。co_await std::suspend_always{};}
}int main() {auto coro generate(8);coro(); // 只会发出第一个元素 0for (int k{}; k 4; k) {coro(); // 每次迭代发出 1 2 3 4 中的一个std::cout : ;}coro.disable_suspension();coro(); // 一次性发出剩余的数字 5 6 7
}输出:
0 1 : 2 : 3 : 4 : 5 6 7 co_yield
co_yield 表达式向调用者返回一个值并暂停当前协程它是可恢复生成器 (generator) 函数的常见构建块。
co_yield expr
co_yield braced-init-list它等价于
co_await promise.yield_value(expr)一个典型的生成器的 yield_value 会将其参数存储复制/移动或仅存储地址因为参数的生命周期在 co_await 内部跨越了暂停点到生成器对象中并返回 std::suspend_always将控制权转移给调用者/恢复者。
示例斐波那契生成器
#include coroutine
#include cstdint
#include exception
#include iostreamtemplatetypename T
struct Generator {// 类名 Generator 是我们的选择对于协程魔法来说不是必需的。// 编译器通过 co_yield 关键字的存在来识别协程。// 你可以使用 MyGenerator (或任何其他名字)只要你包含// 一个带有 MyGenerator get_return_object() 方法的嵌套结构体 promise_type。// (注意重命名时需要调整构造函数和析构函数的声明。)struct promise_type;using handle_type std::coroutine_handlepromise_type;struct promise_type // 必需{T value_;std::exception_ptr exception_;Generator get_return_object() {return Generator(handle_type::from_promise(*this));}std::suspend_always initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }void unhandled_exception() { exception_ std::current_exception(); } // 保存异常templatestd::convertible_toT From // C20 conceptstd::suspend_always yield_value(From from) {value_ std::forwardFrom(from); // 在 promise 中缓存结果return {};}void return_void() {}};handle_type h_;Generator(handle_type h) : h_(h) {}~Generator() { h_.destroy(); }explicit operator bool() {fill(); // 要可靠地判断协程是否结束// 以及是否将通过C的getter(下方的operator())在协程中生成下一个值(co_yield)// 唯一的方法是执行/恢复协程直到下一个co_yield点或让它执行完毕。// 然后我们将结果存储/缓存在 promise 中以允许 getter (下方的operator())// 在不执行协程的情况下获取它。return !h_.done();}T operator()() {fill();full_ false; // 我们将要移出先前缓存的结果使 promise 再次变空return std::move(h_.promise().value_);}private:bool full_ false;void fill() {if (!full_) {h_();if (h_.promise().exception_)std::rethrow_exception(h_.promise().exception_);// 在调用上下文中传播协程异常full_ true;}}
};Generatorstd::uint64_t
fibonacci_sequence(unsigned n) {if (n 0)co_return;if (n 94)throw std::runtime_error(斐波那契序列太大元素会溢出。);co_yield 0;if (n 1)co_return;co_yield 1;if (n 2)co_return;std::uint64_t a 0;std::uint64_t b 1;for (unsigned i 2; i n; i) {std::uint64_t s a b;co_yield s;a b;b s;}
}int main() {try {auto gen fibonacci_sequence(10); // 在 uint64_t 溢出前最大为 94for (int j 0; gen; j)std::cout fib( j ) gen() \n;}catch (const std::exception ex) {std::cerr 异常: ex.what() \n;}catch (...) {std::cerr 未知异常。\n;}
}输出:
fib(0)0
fib(1)1
fib(2)1
fib(3)2
fib(4)3
fib(5)5
fib(6)8
fib(7)13
fib(8)21
fib(9)34注解 (Notes)
特性测试宏
特性宏值标准Coroutines (编译器支持)__cpp_impl_coroutine201902L(C20)Coroutines (库支持)__cpp_lib_coroutine201902L(C20)std::generator__cpp_lib_generator202207L(C23)
关键字
co_await, co_return, co_yield
库支持
协程支持库定义了几个类型为协程提供编译时和运行时的支持。
缺陷报告 (Defect reports)
以下行为变更的缺陷报告被追溯应用于先前发布的C标准。
DR应用于发布时的行为正确的行为CWG 2556C20无效的 return_void 导致函数末尾结束的行为未定义在此情况下程序是病态的 (ill-formed)CWG 2668C20co_await 不能出现在 lambda 表达式中允许CWG 2754C23为显式对象成员函数构造 promise 对象时会捕获 *this在此情况下不捕获 *this