C++20 Coroutines(协程) 详解

原创
08/04 20:08
阅读数 61

Coroutines(协程)的定义及示例

C++20 提供了Coroutines特性,协程是能暂停执行以在之后恢复的函数。协程是无栈的:它们通过返回到调用方暂停执行,并且从栈分离存储恢复所要求的数据。这允许编写异步执行的顺序代码(例如不使用显式的回调来处理非阻塞 I/O),还支持对惰性计算的无限序列上的算法及其他用途。

和函数相比,协程是无栈(stackless)的,局部变量保留在堆内而非栈内,因此可以很方便保留上下文。但是协程不能使用变长参数。

例如,如下代码就使用了协程特性

#if __has_include(<coroutine>)

#include <coroutine>

#else

#include <experimental/coroutine>
namespace std
{
template< class R, class... Args >
using coroutine_traits = std::experimental::coroutine_traits<R, Args...>;

template<class Promise = void>
using coroutine_handle = std::experimental::coroutine_handle<Promise>;

using suspend_always = std::experimental::suspend_always;

using suspend_never = std::experimental::suspend_never;

using noop_coroutine_handle = std::experimental::noop_coroutine_handle;

using noop_coroutine_promise = std::experimental::noop_coroutine_promise;

constinit auto noop_coroutine = std::experimental::noop_coroutine;
}
#endif

#include <iostream>

struct foo_return
{
    using T = foo_return;
    struct promise_type
    {
        T get_return_object()
        {
            return {};
        }
        
        std::suspend_never initial_suspend()
        {
            return {};
        }
        
        std::suspend_never final_suspend() noexcept
        {
            return {};
        }
        
        void unhandled_exception()
        {
        }
        
        void return_void()
        {
        }
    };
};

struct suspend_custom
{
    std::coroutine_handle<> handle;

    bool await_ready() const noexcept
    {
        std::cout << __FUNCTION__ << ": " << __LINE__ << std::endl;
        return false;
    }
    
    void await_suspend(std::coroutine_handle<> handle)
    {
        std::cout << __FUNCTION__ << ": " << __LINE__ << std::endl;
        this->handle = std::coroutine_handle<>::from_address(handle.address());
    }
    
    void await_resume() const noexcept
    {
        std::cout << __FUNCTION__ << ": " << __LINE__ << std::endl;
    }
};

foo_return foo(suspend_custom& a)
{
    for (int i = 0; i < 100; ++i)
    {
        std::cout << i * 10000 + __LINE__ << std::endl;
        co_await a;
        std::cout << i * 10000 + __LINE__ << std::endl;
        co_await a;
        std::cout << i * 10000 + __LINE__ << std::endl;
        co_await a;
    }
    co_return;
}

int main()
{
    suspend_custom a;
    // initial coroutine
    foo(a);
    for(int i = 0; i < 40; ++i)
    {
    // resume coroutine
        a.handle();
    }
    a.handle.destroy();
    return 0;
}

如何使用Coroutines(协程)

例如,上述代码中,foo_return foo(suspend_custom& a)就是一个协程,foo_return类型并非只是单纯返回值类型,而是包含了协程初始化等诸多操作的类型,协程返回值类型必须是一个类,且该类必须包含名为promise的内部类

promise

promise类必须实现如下函数,这些函数由运行时调用,而非用户调用。且协程会在初始化时创建promise对象,promise类型为协程返回类型的名称为promise的内部类。

struct promise_type
{
    T get_return_object();
    
    awaiter initial_suspend();

    awaiter final_suspend() noexcept;

    void unhandled_exception();

    void return_void();
    
    // void return_value(V value);

    awaiter yield_value(V value);
};

get_return_object

被运行时初始化返回对象,在协程开始执行时调用创建返回对象。需要说明的是,协程的返回对象创建时间是在协程开始时,并作为对用户隐藏的局部变量存在。T类型为promise_type所属类的类型。

initial_suspend

初始化完成后被运行时调用,判断初始化后是否挂起。返回awaiter类型,并由该类型成员函数决定挂起操作。

final_suspend

协程执行完毕时被运行时调用,判断完毕后是否挂起。返回awaiter类型,并由该类型成员函数决定挂起操作。

return_void

return_value

执行co_return时被运行时调用,无返回值时调用return_void,否则调用return_value,V类型为co_return后的表达式类型。return_void 和 return_value的定义是互斥的。

yield_value

执行co_yield时被运行时, V类型为co_yield之后表达式的类型,返回值awaiter为等待器的类型,决定挂起操作。

awaiter

awaiter必须实现如下3个函数

struct awaiter
{
    // suspend if return value is false
    bool await_ready() const noexcept;
    
    // call if suspend
    void await_suspend(std::coroutine_handle<> handle);
    
    // call if resume
    void await_resume() const noexcept;
};

awaiter存在2个内置实现std::suspend_neverstd::suspend_always

await_ready

await_ready在执行表达式co_await awaiter时调用,返回值表示是否继续执行函数,如果返回值为false,则调用await_suspend并传入上下文,然后将协程返回到调用方;否则调用await_resume继续执行。对于内置实现std::suspend_never,则该函数恒返回true;std::suspend_always恒返回false。

await_suspend

在协程挂起时调用,传入的handle为协程上下文,可将该上下文返回到协程调用方,并直接调用其成员函数std::coroutine_handle::operator()恢复并执行协程。

await_resume

协程恢复执行时被调用。

std::coroutine_handle

std::coroutine_handle用于保存上下文,被运行时调用创建,其内部存在指针类型指向保存的上下文。

std::coroutine_handle::operator()

从协程挂起处继续执行

std::coroutine_handle::destroy()

显式销毁未结束执行的协程的上下文。注意若协程return结束执行,则会自动销毁上下文,此时不能调用该方法。

协程特有的关键字

co_await

后面必须紧跟awaiter类型,awaiter必须实现3个函数。用于判断协程是否挂起。

co_return

协程结束,并销毁上下文。运行时由该关键字后的类型判定调用promise的return_void或return_value。

co_yield

协程中断,并获取值。返回值为awaiter且决定是否挂起,co_yield expr等价于await promise.yield_value(expr)

协程的编译器内部等价参考实现

协程本质上还是C++语法糖,上述代码等价于下列不使用协程特性的代码。可以看co_await的替换实现。

#if __has_include(<coroutine>)

#include <coroutine>

#else

#include <experimental/coroutine>
namespace std
{
template< class R, class... Args >
using coroutine_traits = std::experimental::coroutine_traits<R, Args...>;

template<class Promise = void>
using coroutine_handle = std::experimental::coroutine_handle<Promise>;

using suspend_always = std::experimental::suspend_always;

using suspend_never = std::experimental::suspend_never;

using noop_coroutine_handle = std::experimental::noop_coroutine_handle;

using noop_coroutine_promise = std::experimental::noop_coroutine_promise;

constinit auto noop_coroutine = std::experimental::noop_coroutine;
}
#endif

#include <iostream>

struct foo_return
{
    using T = foo_return;
    struct promise_type
    {
        T get_return_object()
        {
            return {};
        }

        std::suspend_never initial_suspend()
        {
            return {};
        }
        std::suspend_never final_suspend() noexcept
        {
            return {};
        }
        
        void unhandled_exception()
        {
        }
        
        void return_void()
        {
        }
        
        /*
         void return_value(V value);
         awaiter yield_value(V value);
         */
    };
};

struct suspend_custom
{
    std::coroutine_handle<> handle;

    bool await_ready() const noexcept
    {
        std::cout << __FUNCTION__ << ": " << __LINE__ << std::endl;
        return false;
    }
    
    void await_suspend(std::coroutine_handle<> handle)
    {
        std::cout << __FUNCTION__ << ": " << __LINE__ << std::endl;
        this->handle = std::coroutine_handle<>::from_address(handle.address());
    }
    
    void await_resume() const noexcept
    {
        std::cout << __FUNCTION__ << ": " << __LINE__ << std::endl;
    }
};


struct coroutine_handle_impl
{
    void(&resume)(coroutine_handle_impl*);
    void(&destroy)(coroutine_handle_impl*);
    
    void operator()()
    {
        resume(this);
    }
    
    coroutine_handle_impl(decltype(resume) resume, decltype(destroy) destroy) :
        resume(resume), destroy(destroy)
    {
    }
};

struct _foo : coroutine_handle_impl
{
    using promise_type = foo_return::promise_type;
    
    enum Label
    {
        _1 = 1,
        _2,
        _3,
    };
    
    struct
    {
        // all defined
        promise_type promise;
        Label label;
        foo_return returnObject;
        // custom defined
        suspend_custom& a;
        int i;
    };
    
    static void resume(coroutine_handle_impl* self)
    {
        ((_foo*)self)->operator()();
    }
    
    static void destroy(coroutine_handle_impl* self)
    {
        delete ((_foo*)self);
    }
    
    // stack context
 
    inline void operator()()
    {
        switch (label)
        {
            case _1:
                goto LBL_1;
            case _2:
                goto LBL_2;
            case _3:
                goto LBL_3;
            default:
                break;
        }
        
        for (; i < 100; ++i)
        {
            std::cout << i * 10000 + __LINE__ << std::endl;
            
            // co_await a;
            {
                this->label = _1;
                if(!a.await_ready())
                {
                    a.await_suspend(std::coroutine_handle<>::from_address((void*)this));
                    return;
                }
            LBL_1:
                a.await_resume();
            }
   
            
            std::cout << i * 10000 + __LINE__ << std::endl;
            
            // co_await a;
            {
                this->label = _2;
                if(!a.await_ready())
                {
                    a.await_suspend(std::coroutine_handle<>::from_address((void*)this));
                    return;
                }
            LBL_2:
                a.await_resume();
            }

            std::cout << i * 10000 + __LINE__ << std::endl;
            
            // co_await a;
            {
                this->label = _3;
                if(!a.await_ready())
                {
                    a.await_suspend(std::coroutine_handle<>::from_address((void*)this));
                    return;
                }
            LBL_3:
                a.await_resume();
            }

        }
        
        {
            auto final_suspend = promise.final_suspend();
            if(!final_suspend.await_ready())
            {
                final_suspend.await_suspend(std::coroutine_handle<>::from_address((void*)this));
                return;
            }
        LBL_FINAL:
            final_suspend.await_resume();
        }
        
        delete this;
    }
    
    inline _foo(suspend_custom& a) : coroutine_handle_impl(resume, destroy),  a(a)
    {
        returnObject = promise.get_return_object();
        i = 0;
        label = (Label)0;
        {
            auto initial_suspend = promise.initial_suspend();
            if(!initial_suspend.await_ready())
            {
                initial_suspend.await_suspend(std::coroutine_handle<>::from_address((void*)this));
                return;
            }
            initial_suspend.await_resume();
        }
        operator()();
    }
};

void foo(suspend_custom& a)
{
    new _foo(a);
}

int main()
{
    suspend_custom a;
    foo(a);
    for(int i = 0; i < 40; ++i)
    {
        a.handle();
    }
    a.handle.destroy();
    return 0;
}

展开阅读全文
打赏
0
0 收藏
分享

作者的其它热门文章

加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部