那几年。
大家都在写异步。
网络请求。
磁盘 IO。
定时器。
还有各种“等一下”。
代码也确实更快了。
但人开始更累了。
因为你写着写着。
就不知道自己在哪一层。
当年:回调把控制流撕碎了
C 时代处理异步。
最常见的方式就是回调。
你注册一个函数。
系统在“以后某个时间”再叫你。
这套模型很底层。
也很真实。
但它有个副作用。
控制流不再是从上到下。
它变成了“跳来跳去”。
线上啪一下:我只想写个重试,结果代码长成了树
假设你写个小项目。
要请求一个服务。
失败就重试一次。
成功就解析返回值。
你用回调写着写着。
就会变成这样。
void request_async(std::function<void(int status, std::string body)> cb);
void do_work() {
request_async([&](int st, std::string body) {
if (st != 200) {
request_async([&](int st2, std::string body2) {
if (st2 != 200) return;
// parse(body2)
});
return;
}
// parse(body)
});
}
这段代码的问题。
不是“回调”错了。
问题是。
你想表达的是一条直线。
“请求 → 失败则重试 → 解析”。
你写出来的却是一棵树。
你排障的时候。
人脑要先把这棵树拉直。
很费劲。
协程的想法:让异步代码看起来像同步
协程做的事很克制。
它不承诺让 IO 变快。
它只是把“暂停”和“恢复”。
变成语言级别的控制流。
你写的时候。
还是从上到下。
只是中间可以停。
停完还能接着写。
三个关键字:co_await / co_yield / co_return
co_return
你可以把它当成。
“协程的 return”。
它会把结果交给协程框架。
co_await
你可以把它当成。
“在这里等一下”。
等的不是线程。
等的是一个可等待对象。
等到条件满足。
协程再被恢复。
co_yield
你可以把它当成。
“边算边吐”。
很像生成器。
后面我们会单独讲。
一个最小的协程:先别谈异步,先把语法跑通
协程入门最痛的一点。
是它需要一个返回类型。
返回类型里又要有 promise。
看起来很重。
但你只要接受一个事实。
协程是语法。
框架是你写的那层壳。
先写一个最小 Task:只支持 co_return
#include <coroutine>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() noexcept {}
void unhandled_exception() { std::terminate(); }
};
};
这段代码看起来吓人。
但你先记住两件事就行。
promise_type 是协程和调用者之间的“协议”。
initial_suspend/final_suspend 决定它什么时候挂起。
用起来:它看起来像普通函数
Task hello() {
co_return;
}
int main() {
hello();
}
这里没有异步。
也没有线程。
我们只是证明。
co_return 真的是语言的一部分。
回到现实:co_await 是把“以后再继续”变成语句
你可以把 co_await 想成。
“我把控制权交出去”。
等外部条件满足。
再把控制权还给我。
这就是协程最值钱的地方。
它把控制流。
从回调里。
拉回了函数体里。
关键结论
协程不是新线程。
它是“可暂停的函数”。
你省下的不是 CPU。
是大脑。
小结
如果你只记住一句。
那就是。
协程让异步代码重新变得像直线。
而不是树。