C++20协程示例

C++20协程示例

认识协程

在C++中,协程就是一个可以暂停和恢复的函数。

包含co_waitco_yieldco_return关键字的都可以叫协程。

看一个例子:

MyCoroGenerator<int> testFunc(int n)
{
    std::cout << "Begin testFunc" << std::endl;
    for (int i = 0; i < n; ++i) {
        std::cout << "TestFunc before yield " << i << std::endl;
        co_yield i;
        std::cout << "TestFunc after yield " << i << std::endl;
    }
    std::cout << "End testFunc" << std::endl;
}

int main()
{
    int inp = 10;
    std::cout << "Before testFunc" << std::endl;
    MyCoroGenerator<int> gen = testFunc(inp);
    std::cout << "After testFunc" << std::endl;
    for (int i = 0; i < inp; ++i) {
        std::cout << "Cur input: " << i << std::endl;
        std::cout << "Output value: " << gen.next() << std::endl;
        std::cout << "After input: " << i << std::endl;
    }
}

上面这段代码的执行结果是:

Before testFunc
After testFunc
Cur input: 0
Output value: Begin testFunc
TestFunc before yield 0
0
After input: 0
Cur input: 1
Output value: TestFunc after yield 0
TestFunc before yield 1
1
After input: 1
Cur input: 2
Output value: TestFunc after yield 1
TestFunc before yield 2
2
After input: 2
Cur input: 3
Output value: TestFunc after yield 2
TestFunc before yield 3
3
After input: 3
Cur input: 4
Output value: TestFunc after yield 3
TestFunc before yield 4
4
After input: 4
Cur input: 5
Output value: TestFunc after yield 4
TestFunc before yield 5
5
After input: 5

...

调用时发现,函数并没有一开始就执行,而是等到next时才开始执行。执行到co_yield就会自动暂停,下次next才会继续执行

另外,函数本身并没有返回什么,但是这里可以使用MyCoroGenerator接收。函数的控制权也转移到了MyCoroGenerator,需要执行时就调用next

函数在暂停后执行,依然可以继续上次的状态,这是因为,协程在挂起期间,其上下文全部都被保留,下次执行时恢复。

协程句柄

std::coroutine_handle类模板是实现协程的最底层的工具,负责存储协程的句柄。他可以特化为std::coroutine_handle或者std::coroutine_handle

这里面的Promise是实现协程必要的Promise类,而且其名字必须是promise_type

协程会暂停,其上下文也会保留,std::coroutine_handle就是保存和管理协程的地方。

std::coroutine_handle中方法不多,但是每个都很重要,其中:

  • done方法,可以查询协程是否已经结束
  • resume,恢复一个协程的执行
  • destroy,销毁一个协程

std::coroutine_handle是一个很底层的东西,没有RAII包裹,就像一个裸指针那样,需要手动创建、手动销毁。

所以需要一个包裹类,负责处理协程句柄的初始化和销毁,也就是Generator

Generator

C++的协程要求Generator必须有promise_type这个类,名字也必须这个,不能是其他的名字,直接定义在Generator中最方便。

template <typename T>
struct MyCoroGenerator {
    struct promise_type {
        // todo
    };
};

promise_type中有一些固定接口,这些接口如果不实现,那么协程是不完整的。

  • initial_suspend,协程是否在初始化结束后挂起,返回std::suspend_always、std::suspend_never,这是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起
  • final_suspend,协程最后一次执行是否挂起,返回值和上面函数相同。由于final_suspend是收尾阶段的工作,所以必须是noexcept
  • unhandled_exception,处理协程中未被处理的异常
  • get_return_object,返回一个Generator对象
  • yield_value,处理协程返回值,就是co_yield传递过来的值
  • return_void,处理协程结束后的返回值,和return_value同时只能存在一个,如果没有返回值就用return_void
  • return_value,处理协程结束后返回值,和return_void同时只能存在一个,如果有返回值就用return_value

解释一下yield_value函数,co_yield实际是一个语法糖,相当于co_wait promise.yield_value(i),只有实现了该函数,Genreator才能接收co_yield的返回值

函数的返回值,需要回答协程要不要挂起,也就是std::suspend_always或者std::suspend_never,因为需要yield后挂起,所以返回std::suspend_always

为了接收返回值,需要用转发和std::optional,接收并存储返回值。

get_return_object负责创建一个Generator,一般来说,使用std::coroutine_handle::from_promise接口从一个Promise创建句柄,然后使用这个句柄创建Generator。

为了配合创建函数,MyGenerator需要实现一个接收句柄的构造函数。也就是MyCoroGenerator(std::coroutine_handle h)

#include 
#include 
#include 

template <typename T>
struct MyCoroGenerator {
    /**
     * @brief C++协程要求Generator必须有promise_type这个类型,名字也必须是promise_type
     * 最方便的方法就是定义在Generator类内部
     *
     */
    struct promise_type {
        /**
         * @brief 存放协程返回值
         * 由Generator从获取并返回
         */
        std::optional<T> opt;

        /**
         * @brief 协程是否创建时就被挂起,函数名字也必须是initial_suspend
         * std::suspend_always、std::suspend_never是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起
         *
         * @return std::suspend_always 协程创建即挂起
         */
        std::suspend_always initial_suspend() const
        {
            return {};
        }

        /**
         * @brief 协程最后一次执行是否挂起,函数名字也必须是final_suspend
         * 由于final_suspend是收尾阶段的工作,所以必须是noexcept
         *
         * @return std::suspend_always 协程最后一次执行也被挂起
         */
        std::suspend_always final_suspend() const noexcept
        {
            return {};
        }

        /**
         * @brief 处理协程中未捕获异常,函数名字必须是unhandled_exception
         *
         */
        void unhandled_exception()
        {
            std::exit(EXIT_FAILURE);
        }

        /**
         * @brief 获取一个Generator对象,该对象从promise_type构造
         *
         * @return MyCoroGenerator
         */
        MyCoroGenerator get_return_object()
        {
            return MyCoroGenerator { std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        /**
         * @brief 定制yield_value接口,接收co_yield返回的值
         *
         * @tparam Arg 值的类型
         * @param arg co_yield返回值
         * @return std::suspend_always 执行完后继续挂起
         */
        template <typename Arg>
        std::suspend_always yield_value(Arg&& arg)
        {
            opt.emplace(std::forward<Arg>(arg));
            return {};
        }

        /**
         * @brief 当协程结束co_return且没有返回值时,调用该函数
         * 还有一个return_value(expr)函数,负责处理协程结束且有返回值的情况
         */
        void return_void()
        {
        }
    };

    /**
     * @brief 协程句柄,存储了协程上下文,一个非常底层的东西,没有RAII
     * 用MyCoroGenerator包裹
     */
    std::coroutine_handle<promise_type> handle;

    /**
     * @brief 默认构造函数
     *
     */
    MyCoroGenerator() = default;

    /**
     * @brief 通过一个handle构造一个Generator
     *
     * @param h 从promise_type构造出来的协程句柄
     */
    MyCoroGenerator(std::coroutine_handle<promise_type> h)
        : handle(h)
    {
    }

    /**
     * @brief 移动构造函数
     *
     * @param other 其他Generator对象
     */
    MyCoroGenerator(MyCoroGenerator&& other)
    {
        if (handle) {
            handle.destroy();
        }
        handle = other.handle;
        other.handle = nullptr;
    }

    /**
     * @brief 析构函数
     *
     */
    ~MyCoroGenerator()
    {
        if (handle) {
            handle.destroy();
        }
    }

    /**
     * @brief 移动赋值函数
     *
     * @param other 其他Generator对象
     * @return MyCoroGenerator& 当前镀锡
     */
    MyCoroGenerator& operator=(MyCoroGenerator&& other)
    {
        if (handle) {
            handle.destroy();
        }
        handle = other.handle;
        other.handle = nullptr;
        return *this;
    }

    /**
     * @brief 继续执行协程,并返回执行结果
     *
     * @return T& 返回的值
     */
    T& next()
    {
        handle.resume();
        if (handle.done()) {
            // throw geneator_done("Generator done");
            throw "Generator Error";
        }
        return *(handle.promise().opt);
    }

private:
    MyCoroGenerator(const MyCoroGenerator&) = delete;
    MyCoroGenerator& operator=(const MyCoroGenerator&) = delete;
};

源码

#include 
#include 
#include 

template <typename T>
struct MyCoroGenerator {
    /**
     * @brief C++协程要求Generator必须有promise_type这个类型,名字也必须是promise_type
     * 最方便的方法就是定义在Generator类内部
     *
     */
    struct promise_type {
        /**
         * @brief 存放协程返回值
         * 由Generator从获取并返回
         */
        std::optional<T> opt;

        /**
         * @brief 协程是否创建时就被挂起,函数名字也必须是initial_suspend
         * std::suspend_always、std::suspend_never是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起
         *
         * @return std::suspend_always 协程创建即挂起
         */
        std::suspend_always initial_suspend() const
        {
            return {};
        }

        /**
         * @brief 协程最后一次执行是否挂起,函数名字也必须是final_suspend
         * 由于final_suspend是收尾阶段的工作,所以必须是noexcept
         *
         * @return std::suspend_always 协程最后一次执行也被挂起
         */
        std::suspend_always final_suspend() const noexcept
        {
            return {};
        }

        /**
         * @brief 处理协程中未捕获异常,函数名字必须是unhandled_exception
         *
         */
        void unhandled_exception()
        {
            std::exit(EXIT_FAILURE);
        }

        /**
         * @brief 获取一个Generator对象,该对象从promise_type构造
         *
         * @return MyCoroGenerator
         */
        MyCoroGenerator get_return_object()
        {
            return MyCoroGenerator { std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        /**
         * @brief 定制yield_value接口,接收co_yield返回的值
         *
         * @tparam Arg 值的类型
         * @param arg co_yield返回值
         * @return std::suspend_always 执行完后继续挂起
         */
        template <typename Arg>
        std::suspend_always yield_value(Arg&& arg)
        {
            opt.emplace(std::forward<Arg>(arg));
            return {};
        }

        /**
         * @brief 当协程结束co_return且没有返回值时,调用该函数
         * 还有一个return_value(expr)函数,负责处理协程结束且有返回值的情况
         */
        void return_void()
        {
        }
    };

    /**
     * @brief 协程句柄,存储了协程上下文,一个非常底层的东西,没有RAII
     * 用MyCoroGenerator包裹
     */
    std::coroutine_handle<promise_type> handle;

    /**
     * @brief 默认构造函数
     *
     */
    MyCoroGenerator() = default;

    /**
     * @brief 通过一个handle构造一个Generator
     *
     * @param h 从promise_type构造出来的协程句柄
     */
    MyCoroGenerator(std::coroutine_handle<promise_type> h)
        : handle(h)
    {
    }

    /**
     * @brief 移动构造函数
     *
     * @param other 其他Generator对象
     */
    MyCoroGenerator(MyCoroGenerator&& other)
    {
        if (handle) {
            handle.destroy();
        }
        handle = other.handle;
        other.handle = nullptr;
    }

    /**
     * @brief 析构函数
     *
     */
    ~MyCoroGenerator()
    {
        if (handle) {
            handle.destroy();
        }
    }

    /**
     * @brief 移动赋值函数
     *
     * @param other 其他Generator对象
     * @return MyCoroGenerator& 当前镀锡
     */
    MyCoroGenerator& operator=(MyCoroGenerator&& other)
    {
        if (handle) {
            handle.destroy();
        }
        handle = other.handle;
        other.handle = nullptr;
        return *this;
    }

    /**
     * @brief 继续执行协程,并返回执行结果
     *
     * @return T& 返回的值
     */
    T& next()
    {
        handle.resume();
        if (handle.done()) {
            // throw geneator_done("Generator done");
            throw "Generator Error";
        }
        return *(handle.promise().opt);
    }

private:
    MyCoroGenerator(const MyCoroGenerator&) = delete;
    MyCoroGenerator& operator=(const MyCoroGenerator&) = delete;
};

MyCoroGenerator<int> testFunc(int n)
{
    std::cout << "Begin testFunc" << std::endl;
    for (int i = 0; i < n; ++i) {
        std::cout << "TestFunc before yield " << i << std::endl;
        co_yield i;
        std::cout << "TestFunc after yield " << i << std::endl;
    }
    std::cout << "End testFunc" << std::endl;
}

int main()
{
    int inp = 10;
    std::cout << "Before testFunc" << std::endl;
    MyCoroGenerator<int> gen = testFunc(inp);
    std::cout << "After testFunc" << std::endl;
    for (int i = 0; i < inp; ++i) {
        std::cout << "Cur input: " << i << std::endl;
        std::cout << "Output value: " << gen.next() << std::endl;
        std::cout << "After input: " << i << std::endl;
    }
}

参考:C++ coroutine generator 实现笔记

你可能感兴趣的:(C++,c++20,c++,协程,Coroutine,Promise,线程)