C++20 协程原理与实践

协程是 C++20 引入的一个非常重要的特性,这一特性有助于帮助我们在多任务场景下更高效地编写程序,显著降低代码的理解与维护成本,提升复杂系统的开发效率和可维护性。

1. 协程价值

1.1 问题场景

现代软件开发中,多任务处理需求日益迫切且已成常态。所谓多任务,即程序能同时或交替推进多个独立任务执行,比如处理多个客户端请求。

当前解决多任务问题的核心技术路径常见的有:多线程、IO 多路复用等等。使用这些技术需要深入理解底层技术细节,以及操作系统提供的原始接口,学习难度较大。更关键的是,应用时业务代码往往与技术实现代码深度耦合,最终编写出的程序维护难度大,还极易引发隐蔽的逻辑漏洞。

为了降低技术难度,C++ 标准推出 std::thread、std::async 等封装方案。std::thread 统一了跨平台线程创建接口,std::async 进一步隐藏了线程管理与任务调度的基础逻辑,看似降低了使用成本,但本质上只是对技术语法层面的浅封装。开发者仍需掌握大量技术细节,无法完全聚焦业务流程,想编写出易维护、低 bug 的代码依旧困难重重。


在传统并发模型中,技术细节与业务逻辑天然是交叉在一起的,这种交叉并不只是代码层面的复杂,而是直接改变了开发者的大脑思维方式。比如,我们有 3 个任务需要处理:

在多线程模型中,每个任务通常由一个独立的线程执行。表面上看,这很直观:每个任务仿佛都在自己跑。但问题在于,开发者的大脑必须同时跟踪多个线程的状态:A 线程走到哪一步了?B 和 C 是否在争抢同一个资源?会不会因为调度顺序不同,导致偶尔出错却难以复现?你不是在写一个程序,而是在同时操控好几个并行运行的小程序,还得确保它们互不干扰。

而在 I/O 多路复用模型中,程序把多个 I/O 操作(比如几十个网络连接)一次性注册给操作系统,一旦有任意一个任务的 I/O 就绪,程序就立刻切换过去处理它,处理完再回去等下一个通知。你的代码不再按“任务 A 全部做完 -> 任务 B 全部做完”这样组织,而是被拆成一堆“当 X 就绪时,做 Y 任务”的碎片。


协程的出现,正是为了打破这种困境。协程并不是为了减少代码行数,也不是为了提升性能,而是提供了一种更符合人类直觉的编程方式:它允许我们用线性、顺序的代码来表达看似并发的行为。

比如:对于 2 个任务,先执行任务1,任务一需要等待,于是让出执行权,转而执行任务2,任务2完成后,再回到任务1继续执行。虽然在底层,多个任务可能是并发推进的,但在大脑中,所有任务的处理始终是顺序的、连贯的、可追踪的。这种线性的思维方式,极大地降低了思维负担。当任务很多时,线性的思维方式更有利于人一步一步把事情想清楚,而不是同时顾着好几件事把自己绕晕。


总结:协程的价值并不在于“减少问题的复杂度”,也不在于“减少技术的复杂度”,而是让开发者不再被迫以技术调度的视角思考业务,而是可以站在业务流程本身,用符合人类直觉的线性思维来构建复杂系统。换句话说,协程并没有让问题变简单,而是让人重新以简单的方式理解问题。

1.2 解决思路

在多任务场景下,我们希望以顺序思维方式去编排任务的执行:先做 A 任务,如果 A 任务需要等待外部条件,那就先去做 B 任务,等条件满足后,再回头继续推进 A 任务。这就要求任务在执行过程中能够暂停和恢复。但是,用于封装任务的函数不支持暂停和恢复。

#include <iostream>
#include <thread>
#include <chrono>

// 模拟耗时 I/O
void read_from_network()
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "网络 I/O 结束" << std::endl;
}

// 一个可以立刻完成的任务
void do_other_work()
{
    std::cout << "执行其他任务" << std::endl;
}

int main()
{
    std::cout << "程序开始" << std::endl;

    // 任务 1:等待外部事件(阻塞)
    read_from_network();

    // 任务 2:本可以马上执行
    do_other_work();

    std::cout << "程序结束" << std::endl;
}

函数执行中遇到等待外部事件的操作时,整个执行流会被阻塞,哪怕有其他能立刻推进的任务,也没机会执行。就算强行让函数中途返回,它的执行状态也会彻底销毁,下次调用只能从头再来。

#include <iostream>

// 期望:每次调用返回下一个值
int counter()
{
    int value = 0;
    value++;
    return value;
}

int main()
{
    std::cout << counter() << std::endl; // 1
    std::cout << counter() << std::endl; // 仍然是 1
    std::cout << counter() << std::endl; // 仍然是 1
}

C++20 引入的协程,可以理解为可以保存执行状态、可暂停/恢复的增强函数。执行过程中遇到需要等待外部事件时,可主动暂停执行并让出执行权。当外部条件满足后,协程可以恢复之前保存的状态,并从暂停的位置继续运行。正因为具备这种的能力,协程特别适合用于两类问题场景:

  • IO 密集型多任务场景:适用于需要等待外部 IO 事件完成的场景(如磁盘文件读写、网络请求响应、数据库查询返回),协程在等待 IO 时不阻塞线程,能最大化利用 CPU 资源;
  • 惰性计算的场景:适用于需要将计算过程拆分为多个步骤、按需触发每一步并产出结果的场景(如无限斐波那契数列生成、大数据集分批处理、流式数据计算), 协程执行到某一步后可暂停,直到外部需要下一个结果时再恢复执行。

2. 语法规则

我们从普通函数开始,将其逐步改造为能够支持暂停/恢复的协程函数。

2.1 coroutine_handle

C++ 协程可支持暂停/恢复,编译器为每一个协程提供了 std::coroutine_handle 工具用于管理协程,例如:创建协程、销毁协程、恢复协程、判断协程状态等等。

_EXPORT_STD template <class _CoroPromise>
struct coroutine_handle {
    constexpr coroutine_handle() noexcept = default;
    constexpr coroutine_handle(nullptr_t) noexcept {}

    _NODISCARD static coroutine_handle from_promise(_CoroPromise& _Prom) noexcept { // strengthened
        const auto _Prom_ptr  = const_cast<void*>(static_cast<const volatile void*>(_STD addressof(_Prom)));
        const auto _Frame_ptr = __builtin_coro_promise(_Prom_ptr, 0, true);
        coroutine_handle _Result;
        _Result._Ptr = _Frame_ptr;
        return _Result;
    }

    coroutine_handle& operator=(nullptr_t) noexcept {
        _Ptr = nullptr;
        return *this;
    }

    _NODISCARD constexpr void* address() const noexcept {
        return _Ptr;
    }

    _NODISCARD static constexpr coroutine_handle from_address(void* const _Addr) noexcept { // strengthened
        coroutine_handle _Result;
        _Result._Ptr = _Addr;
        return _Result;
    }

    constexpr operator coroutine_handle<>() const noexcept {
        return coroutine_handle<>::from_address(_Ptr);
    }

    constexpr explicit operator bool() const noexcept {
        return _Ptr != nullptr;
    }

    _NODISCARD bool done() const noexcept { // strengthened
        return __builtin_coro_done(_Ptr);
    }

    void operator()() const {
        __builtin_coro_resume(_Ptr);
    }

    void resume() const {
        __builtin_coro_resume(_Ptr);
    }

    void destroy() const noexcept { // strengthened
        __builtin_coro_destroy(_Ptr);
    }

    _NODISCARD _CoroPromise& promise() const noexcept { // strengthened
        return *reinterpret_cast<_CoroPromise*>(__builtin_coro_promise(_Ptr, 0, false));
    }

private:
    void* _Ptr = nullptr;
};

当创建一个协程的时候,std::coroutine_handle 会在内部创建一块存储空间,用于保存协程所需要的所有状态,这块存储空间称作协程帧。很显然,当协程销毁时,需要释放这块存储空间。为了避免大家忘记释放,C++ 要求我们要通过 RAII 的方式使用 std::coroutine_handle,即:创建一个 RAII 对象,在构造函数中创建 std::coroutine_handle,在析构函数中销毁 std::coroutine_handle

#include <iostream>
#include <coroutine>

struct CoroRAII
{
    std::coroutine_handle<> handle;

    // 构造函数初始化 handle 对象
    CoroRAII(std::coroutine_handle<> handle)
    {
        this->handle = handle;
    }

    // 封装其他 handle 操作
    void resume()
    {
        if (!handle.done())
        {
            handle.resume();
        }
    }

    // 析构函数初始化 handle 对象
    ~CoroRAII()
    {
        if (handle)
        {
            handle.destroy();
        }
    }
};

CoroRAII my_coroutine()
{
    std::cout << "协程执行" << std::endl;
}

int main()
{
    CoroRAII coro = my_coroutine();
    return 0;
}

注意:我们并不能够在 CoroRAII 直接创建一个有效的 std::coroutine_handle 对象,原因有二:

  • std::coroutine_handle 必须基于 _CoroPromise 构建,而 _CoroPromise 目前不知道是什么。
  • CoroRAII 必须由编译器创建,可编译器无法知道如何构建 CoroRAII 对象。

2.2 promise_type

CoroRAII 中,我们需要定义 struct promise_type,它就是构造 std::coroutine_handle 所需要的对象。这个对象作用是什么?

  • 它定义了:协程创建后,是先挂起,还是直接执行?
  • 它定义了:当协程中有未处理的异常时,怎么处理?
  • 它定义了:当协程中返回结果如何处理?
  • 它定义了:当协程逻辑结束是马上销毁,还是稍后由用户销毁?
  • 它定义了: CoroRAII 对象如何构建【重要】

#include <iostream>
#include <coroutine>

struct CoroRAII
{
    struct promise_type
    {
        // 创建协程操作管理对象
        CoroRAII get_return_object()
        {
            return CoroRAII{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        // 决定协程创建后是否马上执行
        // std::suspend_never:不挂起
        // std::suspend_always:挂起
        std::suspend_always initial_suspend()
        {
            return {};
        }

        // 协程执行完后是否马上销毁
        // std::suspend_never:不挂起,立即销毁资源
        // std::suspend_always:挂起,不立即销毁,等待外部 destroy 释放资源
        std::suspend_always final_suspend() noexcept
        {
            return {};
        }

        // 协程中有未处理的异常时如何处理
        void unhandled_exception()
        {
            std::cout << "未处理的异常" << std::endl;
        }
    };



    std::coroutine_handle<> handle;
    CoroRAII(std::coroutine_handle<promise_type> handle)
    {
        this->handle = handle;
    }

    void resume()
    {
        if (!handle.done())
        {
            handle.resume();
        }
    }

    ~CoroRAII()
    {
        if (handle)
        {
            handle.destroy();
        }
    }
};

CoroRAII my_coroutine()
{
    std::cout << "协程执行" << std::endl;
}

int main()
{
    CoroRAII coro = my_coroutine();
    return 0;
}

到这里,需要同学们注意下协程的创建流程,编译器会:

  1. 首先,创建 promise_type 对象
  2. 然后,调用 promise_type::get_return_object() 函数返回协程管理对象
  3. 接着,调用 promise_type::initial_suspend() 函数确定协程初始化后是否马上执行
  4. 如果,协程中抛出了未处理的异常,promise_type::unhandled_exception() 函数会被执行处理异常
  5. 最后,协程结束之后,调用 promise_type::final_suspend() 函数来确定马上销毁协程,还是用户手动销毁

从语法角度,编译器还是无法确定 my_coroutine 是普通函数,还是协程函数。所以,编译器又做了一个要求,对于协程函数 my_coroutine,内部必须出现协程关键字:co_returnco_yield、或者 co_await

我们总结一下,在 C++20 中,一个函数被编译器判定为协程,需同时满足以下两个核心条件:

  • 函数内部要求:函数体内部必须显式使用 co_returnco_yieldco_await 中的至少一个协程关键字。
  • 返回类型要求:返回类型中必须嵌套定义合法的 promise_type 结构体,缺失会编译报错。

2.3 co_return

co_return 可以直接结束协程逻辑、或者返回一个值再结束协程逻辑,当协程逻辑结束后,会自动调用 promise_type::final_suspend 函数 。

注意:协程逻辑结束并不代表协程对象立即销毁,协程对象是否随着协程逻辑结束而销毁是由 final_suspend 的返回值决定。

  • std::suspend_never → 协程立即销毁,资源释放
  • std::suspend_always → 协程挂起,由外部显式调用 destroy() 才销毁

co_return 没有返回值时,编译器会调用 `void promise_type::return_void()` 函数来处理返回逻辑,然后执行 final_suspend() 函数,根据其返回值决定协程是否销毁。

#include <iostream>
#include <coroutine>

struct CoroRAII
{
    struct promise_type
    {
        CoroRAII get_return_object()
        {
            return CoroRAII{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        std::suspend_always initial_suspend()
        {
            return {};
        }

        void return_void()
        {
            std::cout << "协程结束" << std::endl;
        }

        std::suspend_always final_suspend() noexcept
        {
            return {};
        }

        void unhandled_exception()
        {
            std::cout << "未处理的异常" << std::endl;
        }
    };

    std::coroutine_handle<promise_type> handle;
    CoroRAII(std::coroutine_handle<promise_type> handle)
    {
        this->handle = handle;
    }

    void resume()
    {
        if (handle && !handle.done())
        {
            handle.resume();
        }
    }

    ~CoroRAII()
    {
        if (handle)
        {
            handle.destroy();
        }
    }
};

CoroRAII my_coroutine()
{
    std::cout << "协程开始" << std::endl;
    co_return;
}

int main()
{
    CoroRAII coro = my_coroutine();
    coro.resume();
    return 0;
}


co_return 有返回值时,它会将返回值传递到 void promise_type::return_value(T value) 函数中处理,然后执行 final_suspend() 函数,根据其返回值决定协程是否销毁。注意:

  • co_return 返回值需要通过 promise_type 的成员变量存储与获取,建议封装返回值获取接口,避免外部直接操作 handlepromise 对象。
  • return_valuereturn_void 互斥,即:协程函数要不有返回值、要不没有返回值

#include <iostream>
#include <coroutine>

struct CoroRAII
{
    struct promise_type
    {
        int value;

        CoroRAII get_return_object()
        {
            return CoroRAII{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        std::suspend_always initial_suspend()
        {
            return {};
        }

        // 2. 存储结果
        void return_value(int value)
        {
            this->value = value;
        }

        std::suspend_always final_suspend() noexcept
        {
            return {};
        }

        void unhandled_exception()
        {
            std::cout << "未处理的异常" << std::endl;
        }
    };

    std::coroutine_handle<promise_type> handle;
    CoroRAII(std::coroutine_handle<promise_type> handle)
    {
        this->handle = handle;
    }

    // 3. 返回结果
    int value()
    {
        return handle.promise().value;
    }

    void resume()
    {
        if (handle && !handle.done())
        {
            handle.resume();
        }
    }

    ~CoroRAII()
    {
        if (handle)
        {
            handle.destroy();
        }
    }
};

CoroRAII my_coroutine()
{
    std::cout << "协程开始" << std::endl;
    // 1. 返回结果
    co_return 100;
}

int main()
{
    CoroRAII coro = my_coroutine();
    coro.resume();
    std::cout << coro.value() << std::endl;

    return 0;
}


当协程函数在执行时,抛出异常,但是并没有处理异常时,编译器会自动调用:void promise_type::unhandled_exception() 来处理。

#include <iostream>
#include <coroutine>

struct CoroRAII
{
    struct promise_type
    {
        int value;

        CoroRAII get_return_object()
        {
            return CoroRAII{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        std::suspend_always initial_suspend()
        {
            return {};
        }

        std::suspend_always final_suspend() noexcept
        {
            return {};
        }

        void return_void()
        {

        }

        void unhandled_exception()
        {
            std::cout << "未处理的异常" << std::endl;
        }
    };

    std::coroutine_handle<promise_type> handle;
    CoroRAII(std::coroutine_handle<promise_type> handle)
    {
        this->handle = handle;
    }

    void resume()
    {
        if (handle && !handle.done())
        {
            handle.resume();
        }
    }

    // 获得协程结果
    int value()
    {
        if (handle)
        {
            return handle.promise().value;
        }
        return 0;
    }

    ~CoroRAII()
    {
        if (handle)
        {
            handle.destroy();
        }
    }
};

CoroRAII my_coroutine()
{
    std::cout << "协程开始" << std::endl;
    throw std::runtime_error("抛出异常");
    co_return;
}

int main()
{
    CoroRAII coro = my_coroutine();
    coro.resume();

    return 0;
}

2.4 co_yield

与 co_return 直接终止协程执行不同,co_yield 并不会结束协程,而是先向调用方生成一个中间值,再将自身挂起。它无需依赖任何外部事件,完全由协程主动触发挂起,后续需等待调用方显式恢复才能继续执行。

具体的执行流程,当协程执行到 co_yield value; 时:会调用 auto promise_type::yield_value(T value) 函数,其返回值决定协程在 co_yield 后是否立即挂起:

  • std::suspend_always:协程挂起,调用者可以从外部通过 resume() 恢复执行
  • std::suspend_never:协程不挂起,继续执行

#include <iostream>
#include <coroutine>

struct CoroManager
{
    struct promise_type
    {
        int value;

        CoroManager get_return_object()
        {
            return CoroManager{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        std::suspend_always initial_suspend() { return {}; }
        void return_void() {}

        // std::suspend_always: 挂起;
        // std::suspend_never:不挂起
        std::suspend_always yield_value(int value)
        {
            this->value = value;
            return {};
        }

        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;
    CoroManager(std::coroutine_handle<promise_type> handle)
    {
        this->handle = handle;
    }

    ~CoroManager()
    {
        if (handle)
        {
            handle.destroy();
        }
    }
};

CoroManager my_coroutine()
{
    co_yield 10;
    co_yield 20;
    co_yield 30;

    co_return;
}

int main()
{
    CoroManager coro = my_coroutine();

    coro.handle.resume();
    std::cout << "第一次返回的值:" << coro.handle.promise().value << std::endl;

    coro.handle.resume();
    std::cout << "第二次返回的值:" << coro.handle.promise().value << std::endl;

    coro.handle.resume();
    std::cout << "第三次返回的值:" << coro.handle.promise().value << std::endl;

    return 0;
}

2.5 co_await

和 co_yield 无外部依赖的特性不同,co_await 必须等待外部事件就绪才能继续:比如 IO 完成、定时器到期,等待期间协程会挂起,事件就绪后再恢复执行。这也决定了 co_await 后面不能是简单的表达式,而是一个可等待对象(实现 awaitable 协议的对象)。

struct Awaiter
{
    // 是否继续执行
    // true 表示继续执行,然后执行 await_resume() 返回结果
    // false 表示挂起,然后执行 await_suspend() 进行挂起前的操作
    bool await_ready();
    
    // 返回 void:执行后挂起
    // 返回 bool:true 表示执行完后挂起,false 表示执行完后不挂起,继续执行 await_resume 函数
    // 返回 handle:表示执行完后,继续恢复执行 handle 关联的协程
    void/bool/handle await_suspend(std::coroutine_handle<> handle);

    // 协程恢复后执行,返回 co_await 的结果
    T await_resume();
};

#include <iostream>
#include <coroutine>

struct MyAwaiter
{
    int value;

    // 1. 是否直接执行
    bool await_ready() const noexcept
    {
        bool flag = true;
        std::cout << "是否执行:" << std::boolalpha << flag << std::endl;
        // true 不挂起:直接执行 await_resume 函数
        // false 挂起:直接执行 await_suspend 函数
        return flag;
    }

    // 2. 挂起时调用
    void await_suspend(std::coroutine_handle<> h) noexcept
    {
        std::cout << "协程挂起" << std::endl;
    }

    // 3. 恢复时调用
    int await_resume() noexcept
    {
        std::cout << "协程执行" << std::endl;
        return 100;
    }
};

struct CoroManager
{
    struct promise_type
    {
        int value;

        CoroManager get_return_object()
        {
            return CoroManager{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        std::suspend_always initial_suspend()
        {
            return {};
        }

        void return_void()
        {
            std::cout << "协程结束" << std::endl;
        }

        std::suspend_always final_suspend() noexcept
        {
            return {};
        }

        void unhandled_exception()
        {
            std::terminate();
        }
    };

    std::coroutine_handle<promise_type> handle;
    CoroManager(std::coroutine_handle<promise_type> handle)
    {
        this->handle = handle;
    }

    ~CoroManager()
    {
        if (handle)
        {
            handle.destroy();
            std::cout << "协程销毁" << std::endl;
        }
    }
};

CoroManager coro_function()
{
    int ret = co_await MyAwaiter();
    std::cout << "协程结果: " << ret << std::endl;
    co_return;
}

int main()
{
    CoroManager coro = coro_function();
    coro.handle.resume();

    return 0;
}


promise_type 中定义 await_transform,可以拦截 co_await 后的表达式,并将其转换为一个满足 awaitable 协议的对象,无论原始表达式本身是否可等待。

#include <iostream>
#include <coroutine>

struct MyAwaiter
{
    int value;
    bool await_ready() { return false; }
    void await_suspend(std::coroutine_handle<> handle) {}
    int await_resume() { return value; }
};

struct CoroManager
{
    struct promise_type
    {
        int value;

        CoroManager get_return_object() { return CoroManager{ std::coroutine_handle<promise_type>::from_promise(*this) };}
        std::suspend_always initial_suspend() { return {}; }
        void return_void() {}
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() {}

        auto await_transform(int val)
        {
            return MyAwaiter{ val };
        }
    };

    std::coroutine_handle<promise_type> handle;
    CoroManager(std::coroutine_handle<promise_type> handle)
    {
        this->handle = handle;
    }

    void resume() { handle.resume(); }
    ~CoroManager(){ handle.destroy(); }
};

CoroManager my_croutine()
{
    int ret = co_await 100;
    std::cout << "ret = " << ret << std::endl;
}

int main()
{
    CoroManager coro = my_croutine();
    coro.resume();
    coro.resume();

    return 0;
}

3. 协程帧

协程帧是编译器为协程生成的一块独立存储区域,用于保存协程跨 co_await/co_yield 边界时必须保留的全部状态。它承载的内容包括局部变量、promise 对象、当前执行位置以及挂起点信息,是协程实现挂起与恢复执行的核心数据结构。

在 C++20 协程中,当一个协程被创建时,编译器默认会通过 new 为其分配协程帧(coroutine frame)所需的内存;当协程执行完毕并销毁时,则通过 delete 释放该内存。

这种默认行为对于大多数普通应用场景是足够安全且高效的。然而,在某些高性能或高并发场景下(例如每秒需要创建成千上万个短生命周期协程的服务器程序),频繁调用 new/delete 会带来显著的性能开销:不仅增加了内存分配/回收的 CPU 时间,还可能引发内存碎片,进而成为系统瓶颈。

为了解决这个问题,C++ 允许开发者通过自定义协程的内存分配策略(例如重载 operator newoperator delete)来接管协程帧的内存管理。这样可以大幅降低分配成本、提升内存访问局部性,并使性能表现更加可预测。

但需要注意的是,自定义内存管理会增加代码复杂度和维护成本。因此,除非你的程序确实面临协程数量庞大、生命周期极短、且内存分配已成为性能瓶颈的情况,否则通常没有必要放弃默认的内存管理机制。

#include <iostream>
#include <coroutine>

struct CoroManager
{
    struct promise_type
    {
        static void* operator new(size_t size)
        {
            void* ptr = ::operator new(size);
            std::cout << "分配协程帧内存: " << ptr << " " << size << std::endl;
            return ptr;
        }

        static void operator delete(void* ptr)
        {
            std::cout << "释放协程帧内存: " << ptr << std::endl;
            ::operator delete(ptr);
        }

        CoroManager get_return_object() { return CoroManager{ std::coroutine_handle<promise_type>::from_promise(*this) };}
        std::suspend_always initial_suspend() { return {}; }
        void return_void(){ std::cout << "协程结束" << std::endl; }
        std::suspend_always final_suspend() noexcept { return {};}
        void unhandled_exception(){}
    };

    std::coroutine_handle<promise_type> handle;
    CoroManager(std::coroutine_handle<promise_type> handle) : handle(handle){}
    void resume(){ handle.resume(); }
    ~CoroManager(){ handle.destroy();}
};

CoroManager my_coroutine()
{
    std::cout << "协程开始" << std::endl;
    co_return;
}

int main()
{
    CoroManager coro = my_coroutine();
    coro.resume();
    return 0;
}

4. 应用案例

4.1 惰性计算

#include <iostream>

int fibonacci()
{
    static int prev1 = 0;
    static int prev2 = 1;

    int current = prev1;
    int next = prev1 + prev2;

    prev1 = prev2;
    prev2 = next;

    return current;
}

void demo()
{
    for (int i = 0; i < 5; ++i)
    {
        int val = fibonacci();
        std::cout << val << std::endl;
    }
}

int main()
{
    demo();
    return 0;
}

#include <coroutine>
#include <iostream>


struct Generator
{
    struct promise_type
    {
        int value;

        Generator get_return_object()
        {
            return Generator{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        std::suspend_always initial_suspend()
        {
            return {};
        }

        std::suspend_never final_suspend() noexcept
        {
            return {};
        }

        std::suspend_always yield_value(int value)
        {
            this->value = value;
            return {};
        }

        void return_void()
        {
            std::cout << "协程结束" << std::endl;
        }

        void unhandled_exception()
        {
            std::terminate();
        }
    };

    std::coroutine_handle<promise_type> handle;
    explicit Generator(std::coroutine_handle<promise_type> handle)
    {
        this->handle = handle;
    }

    // 获取下一个值(返回是否有值)
    bool next()
    {
        // 协程的执行状态:
        // true: 协程已完成,无法再通过 resume() 恢复执行
        // false: 协程处于挂起态,可以通过 resume() 恢复执行
        if (handle.done())
        {
            return false;
        }
        // 恢复协程执行
        handle.resume();

        // 返回协程状态
        return !handle.done();
    }

    // 获取当前值
    int value() const
    {
        return handle.promise().value;
    }

    ~Generator()
    {
        if (handle)
        {
            handle.destroy();
        }
    }
};

Generator fibonacci()
{
    int prev1 = 0;
    int prev2 = 1;

    while (true)
    {
        int current = prev1;
        int next = prev1 + prev2;

        prev1 = prev2;
        prev2 = next;

        co_yield current;
    }
}

int main()
{
    // 创建协程
    auto gen = fibonacci();

    for (int i = 0; i < 10; ++i)
    {
        // 恢复协程
        bool flag = gen.next();

        if (!flag)
        {
            break;
        }

        // 获得数值
        std::cout << gen.value() << std::endl;
    }

    return 0;
}

4.2 异步编程

异步编程是一种以避免发起任务的执行线程被阻塞为目标的编程思想。 它通过将等待操作从当前执行路径中剥离,使线程在等待期间仍能继续处理其他任务,从而提升线程的整体利用效率。 在工程实现上,异步编程通常会结合多线程、线程池、多路 I/O 复用、定时器等机制来承载和管理这些等待。

#include <iostream>
#include <chrono>
#include <thread>

void task1()
{
    std::cout << "task1 执行开始" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "task1 执行结束" << std::endl;

}

void task2()
{
    std::cout << "task2 执行开始" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "task2 执行结束" << std::endl;
}


int main()
{
    auto start = std::chrono::steady_clock::now();

    task1();
    task2();

    auto end = std::chrono::steady_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "程序耗时:" << duration.count() << "毫秒" << std::endl;

    return 0;
}

#include <iostream>
#include <coroutine>
#include <chrono>
#include <thread>
#include <queue>
#include <mutex>


class EventLoop
{
public:

    void add_task(std::coroutine_handle<> handle)
    {
        {
            std::lock_guard<std::mutex> lock(my_mutex);
            my_queue.push(handle);
        }
        ++number;
    }

    void post(std::coroutine_handle<> handle)
    {
        {
            std::lock_guard<std::mutex> lock(my_mutex);
            my_queue.push(handle);
        }
        my_cv.notify_one();
    }

    void run()
    {
        while (true)
        {
            std::coroutine_handle<> my_handle;
            {
                std::unique_lock<std::mutex> lock(my_mutex);

                // 如果队列为非空,结束阻塞
                // 如果任务数为零,结束阻塞
                my_cv.wait(lock, [&] { return !my_queue.empty() || 0 == number; });

                if (my_queue.empty() && 0 == number)
                {
                    std::cout << "任务执行完毕" << std::endl;
                    break;
                }

                my_handle = my_queue.front();
                my_queue.pop();
            }

            if (!my_handle)
            {
                continue;
            }

            my_handle.resume();

            if (my_handle.done())
            {
                --number;
                my_handle.destroy();
            }
        }
    }

private:
    std::queue<std::coroutine_handle<>> my_queue;
    std::mutex my_mutex;
    std::condition_variable my_cv;
    size_t number{ 0 };
};

EventLoop eloop;


struct Task
{
    struct promise_type
    {
        Task get_return_object()
        {
            return Task{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        std::suspend_always initial_suspend()
        {
            return {};
        }

        std::suspend_always final_suspend() noexcept
        {
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::exit(1); }
    };
    operator std::coroutine_handle<>()
    {
        return handle;
    }
    std::coroutine_handle<promise_type> handle;
    Task(std::coroutine_handle<promise_type> h) : handle(h) {}
    void resume() { handle.resume(); }
};

struct AsyncSleep
{
    int time;

    // 挂起协程
    bool await_ready()
    {
        return false;
    }

    void await_suspend(std::coroutine_handle<> handle)
    {
        // 模拟异步:另起线程等待后恢复协程
        std::thread([handle, duration = time] {

            std::this_thread::sleep_for(std::chrono::seconds(duration));
            eloop.post(handle);

            }).detach();
    }

    void await_resume() {}
};

Task task1()
{
    std::cout << "task1 执行开始" << std::endl;
    co_await AsyncSleep{ 5 };
    std::cout << "task1 执行结束" << std::endl;

}

Task task2()
{
    std::cout << "task2 执行开始" << std::endl;
    co_await AsyncSleep{ 3 };
    std::cout << "task2 执行结束" << std::endl;
}


int main()
{
    auto start = std::chrono::steady_clock::now();

    auto t1 = task1();
    auto t2 = task2();
    eloop.add_task(t1);
    eloop.add_task(t2);
    eloop.run();

    auto end = std::chrono::steady_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "程序耗时:" << duration.count() << "毫秒" << std::endl;

    return 0;
}

未经允许不得转载:一亩三分地 » C++20 协程原理与实践
评论 (0)

2 + 9 =