C语言中协程函数如何实现状态机机制?

摘要:C++20 协程能让出控制权、能继续执行、没有线程栈的切换,看起来似乎很神奇,然而我用实打实的代码告诉你它就是个函数+状态机,你会不会拍桌子骂娘并从此对协程祛魅?
前言 之前写过一篇 C++20 协程入门的文章:《使用 C++ 20 协程降低异步网络编程复杂度》,谈到了协程在消除异步编程 callback hell 方面的重要贡献——使代码更清晰,易于维护;以及 C++20 协程具有无栈、非对称等特性。无栈协程具有不受预分配栈空间约束、切换类似函数开销更小的优点,符合 C++ 语言设计原则中的 no payload 理念 (不因新增加的语言特性而增加额外性能负担);非对称表示协程控制权的转移是单向的,即通过 co_await/co_yield 挂起时,必需返回到调用者最初的上下文,而不能随意切换到其它协程,这样做逻辑清晰,便于调试。 C++20 协程相对的缺点就是概念繁多、过于灵活,特别是编译器在底层默默的做了很多工作,使得调用链经常断掉不好理解,之前的文章讲到原理就草草贴了几张流程图了事,今天要把这个原理掰开了好好说道一番。 讲 C++20 协程,除了协程本身的复杂性,还有新标准带来的新特性,每次新的标准面世,就像是换了个语言,各种语法糖能大大提升开发效率,但也提升了理解成本。以插入 map 元素这个小功能为例,看看各个标准是如何演化的。 我们知道,std::map 在 insert 时如果元素已经存在是不会替换元素的,而是返回一个指示元素所在位置的 iterator 和是否插入成功的标志: #include <iostream> #include <map> int main() { std::map<int, int> mp; // mp.insert(std::make_pair(1, 2)); std::pair<std::map<int, int>::iterator, bool> result = mp.insert(std::make_pair(1, 1)); if (result.second) std::cout << "inserted" << std::endl; for (std::map<int, int>::iterator itr = mp.begin(); itr != mp.end(); ++itr) { std::cout << "{" << itr->first << ", " << itr->second << "}" << std::endl; } return 0; } 输出: inserted {1, 1} 这是 C++98 标准就支持的语法,map::insert 返回值为 std::pair,其 first 为容器 iterator 用于标识插入或已有元素位置,其 second 为 bool 表示是否插入成功。下面看下 C++11 的改进: #include <iostream> #include <map> #include <tuple> int main() { std::map<int, int> mp; // = { {1,3} }; bool inserted; std::tie(std::ignore, inserted) = mp.insert({1, 1}); if (inserted) std::cout << "inserted" << std::endl; // for (auto itr = mp.begin(); itr != mp.end(); ++itr) { for(auto itr : mp) { std::cout << "{" << itr.first << ", " << itr.second << "}" << std::endl; } return 0; } 输出一致。主要改进在于通过 tie 将 inserted 变量绑定到返回的 tuple 结构中 (pair 也是 tuple 的一种),之后直接引用 inserted 变量,而不是不明就里的 first & second,代码可读性更强了并且没有额外的对象拷贝。
阅读全文