C ++ 20。协程

在本文中,我们将详细分析协程的概念,它们的分类,并仔细研究新C ++ 20标准提供的实现,假设和权衡。



图片



一般信息



协程可以看作是例程(函数)在操作上的概括。协程和子例程之间的根本区别在于,协程提供了显式暂停其执行的能力,将控制权交给其他程序单元,并在获得控制权的同时使用其他操作在恢复原始点的同时恢复其工作,同时保持本地数据(执行状态)。在连续调用之间,从而提供了更灵活和扩展的控制流程。



为了弄清这个定义和进一步的推理,并引入辅助概念和术语,请考虑C ++中普通函数的机制及其堆栈性质。



我们将在两个操作的上下文中考虑函数的语义。



(call). . :



  1. (activation record, activation frame), ;
  2. ( ) , ;
  3. . ;
  4. — , .


, .



(return). . :



  1. ( ) ;
  2. , ;
  3. .


, . ().



:



  1. (strictly nested lifetime) . , : . .
  2. .


, . — , : ss ( ), bp ( ), sp ( ), ( ). , , .



. , (Calling Convention). , . ( ) . , , , .



:



void bar(int a, int b)
{}

void foo()
{
    int a = 1;
    int b = 2;
    bar(a, b);
}

int main()
{
    foo();
}


- (x86-64 clang 10.0.0 -m32,

32 . 64 , , , ):



bar(int, int):
        push    ebp
        mov     ebp, esp
        mov     eax, dword ptr [ebp + 12]
        mov     ecx, dword ptr [ebp + 8]
        pop     ebp
        ret
foo():
        push    ebp 
        mov     ebp, esp
        sub     esp, 24 
        mov     dword ptr [ebp - 4], 1
        mov     dword ptr [ebp - 8], 2
        mov     eax, dword ptr [ebp - 4]
        mov     ecx, dword ptr [ebp - 8]
        mov     dword ptr [esp], eax
        mov     dword ptr [esp + 4], ecx
        call    bar(int, int)
        add     esp, 24
        pop     ebp
        ret
main:
        push    ebp
        mov     ebp, esp
        sub     esp, 8  
        call    foo()
        xor     eax, eax
        add     esp, 8
        pop     ebp
        ret


:



main, , ebp ( ) .. , ebp esp ( )



| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp, esp
+----------------+


foo. . .. 16 8 (4 4 ebp) 8 , .



| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| ...            |
| 8 byte padding |
| ...            |     <-- esp
-----------------+


foo. call . foo ebp ( ) ebp esp ( ), .



| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| ...            |
| 8 byte padding |
| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp, esp
+----------------+


bar. int — 8 , bar int — 8 . .. 8 ( ebp) 8 . 8 + 8 + 8 = 24 , .



| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| ...            |
| 8 byte padding |
| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| local a        |     <-- ebp - 4
+----------------+
| local b        |     <-- ebp - 8
+----------------+
| ...            |
| 8 byte padding |
| ...            |
+----------------+
| arg a          |     <-- esp + 4
+----------------+
| arg b          |     <-- esp
+----------------+


bar. , foo. call . bar ebp ebp esp, .



| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| ...            |
| 8 byte padding |
| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| local a        |     <-- ebp - 4
+----------------+
| local b        |     <-- ebp - 8
+----------------+
| ...            |
| 8 byte padding |
| ...            |
+----------------+
| arg a          |     <-- ebp + 12
+----------------+
| arg b          |     <-- ebp + 8
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp, esp
+----------------+


bar . ebp ( , foo) , 4 . 4 . foo.



| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| ...            |
| 8 byte padding |
| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| local a        |     <-- ebp - 4
+----------------+
| local b        |     <-- ebp - 8
+----------------+
| ...            |
| 8 byte padding |
| ...            |
+----------------+
| arg a          |     <-- esp + 4
+----------------+
| arg b          |     <-- esp
+----------------+


foo bar . , 24 .



| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| ...            |
| 8 byte padding |
| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp, esp
+----------------+


ebp ( , main) . . main.



| ...            |
+----------------+
| return address |
+----------------+
| saved rbp      |     <-- ebp
+----------------+
| ...            |
| 8 byte padding |
| ...            |     <-- esp
-----------------+


main , , .



| ...            |
+----------------+
| return address |
+----------------+


, , .





, .



  1. ;
  2. ;
  3. ( ).


(symmetric) (asymmetric, semi-symmetric).



, , . : , . , , , .



: , , .



.

A , .



(first-class object, first-class citizen) (constrained, compiler-internal), (handles), .



— , , , ( ). , , . , (function object): , , .



(stackful) (stackless). , , (proccesor stack).



:



  • (Application stack). main. . , ;
  • (Thread stack). . ( 1-2 );
  • (Side stack). (Execution context) , (top level context function, ) . ( ), . : , , .


. — ( , : ) . , - , , .



, c: getcontext, makecontext swapcontext (. Complete Context Control)



#include <iostream>
#include <ucontext.h>

static ucontext_t caller_context;
static ucontext_t coroutine_context;

void print_hello_and_suspend()
{
     //  Hello 
    std::cout << "Hello";
    //     , 
    //    caller_context
    //    coroutine_context    ,
    //   ,     .
    swapcontext(&coroutine_context, &caller_context);
}

void simple_coroutine()
{
    //      coroutine_context
    //     
    //     print_hello_and_suspend.
    print_hello_and_suspend();
    //  print_hello_and_suspend   
    //        Coroutine!   ,
    //    ,
    //      coroutine_context.uc_link, .. caller_context
    std::cout << "Coroutine!" << std::endl;
}

int main()
{
    //  .
    char stack[256];

    //    coroutine_context
    // uc_link   caller_context,     .
    // uc_stack     
    coroutine_context.uc_link          = &caller_context;
    coroutine_context.uc_stack.ss_sp   = stack;
    coroutine_context.uc_stack.ss_size = sizeof(stack);
    getcontext(&coroutine_context);

    //  coroutine_context
    //    ,    
    //        simple_coroutine
    makecontext(&coroutine_context, simple_coroutine, 0);

    //   ,    coroutine_context
    //   caller_context    ,
    //   ,     .
    swapcontext(&caller_context, &coroutine_context);
    //       
    //  
    std::cout << " ";
    //    .
    swapcontext(&caller_context, &coroutine_context);

    return 0;
}


, Boost: Boost.Coroutine, Boost.Coroutine2, Boost ucontext_t fcontext_t — , (, / , ) POSIX .



, , , . , , , , . , , , , .. . .



:



  1. (top level function), ;
  2. ;
  3. , , ;
  4. ( ), , .


, C++20, , , .



C++20.



C++ Coroutine TS. Coroutine TS , , .



. range based for, , , begin end, , , . , , .



compile-internal .



, , . , -, , , , -, ( ), .



, , .. .



, C++20 compile-internal asymmetric stackless coroutines.



, , .



New Keywords.



:



  • co_await. , , , , ;
  • co_yield. , co_await, ;
  • co_return. , , .


, .



:



  • main;
  • return;
  • constexpr;
  • (auto);
  • (variadic arguments, variadic templates);
  • ;
  • .


User types.



, .



Promise.



Promise . :



  • ;
  • ;
  • ;
  • co_await;
  • .

    promise new delete, .

    promise .


Promise std::coroutine_traits , : , , , . std::coroutine_traits :



template <typename Ret, typename = std::void_t<>>
struct coroutine_traits_base
{};

template <typename Ret>
struct coroutine_traits_base<Ret, std::void_t<typename Ret::promise_type>>
{
    using promise_type = typename Ret::promise_type;
};

template <typename Ret, typename... Ts>
struct coroutine_traits : coroutine_traits_base<Ret>
{};


promise_type. std::coroutine_traits, , promise_type . promise_type , .



Promise .



struct Task
{
    struct Promise
    {
        ...
    };
    using promise_type = Promise;
};
...

Task foo()
{
    ...
}


Task , : , () .



Promise — std::coroutine_traits. , ,



class Coroutine
{
public:
    void call(int);
};

namespace std
{
    template<>
    struct coroutine_traits<void, Coroutine, int>
    {
        using promise_type = Coroutine;
    };
}


Promise , , . , lvalues, Promise .. . Promise .



Promise, : Awaitable.



Awaitable.



Awaitable . :



  • , co_await;
  • ( );
  • co_await, .


Awaitable (overload resolution) co_await. , Awaitable. .



, , , , .



Task foo()
{
    using namespace std::chrono_literals;

    //    
    //  
    co_await 10s;
    //  10      .
}


std::chrono::duration<long long>, co_await .



template<typename Rep, typename Period>
auto operator co_await(std::chrono::duration<Rep, Period> duration) noexcept
{
    struct Awaitable
    {
        explicit Awaitable(std::chrono::system_clock::duration<Rep, Period> duration)
            : duration_(duration)
        {}

        ...

    private:

        std::chrono::system_clock::duration duration_;
    };

    return Awaitable{ duration };
}


Awaitable, , .



Awaitable, co_await <expr> , .



{
    //      Promise
    using coroutine_traits = std::coroutine_traits<ReturnValue, Args...>;
    using promise_type = typename coroutine_traits::promise_type;

    ...
    //  co_await <expr>   

    // 1.
    //    Awaitable,     co_await,
    //      (    
    //     Promise),   
    // ..   Awaitable    ,   .
    frame->awaitable = create_awaitable(<expr>);

    // 2.
    //   await_ready().
    //        
    //        
    //   ,   .
    if (!awaitable.await_ready())
    {
        // 3.
        //   await_ready()  false,
        //     ,
        //  :   ,  
        // (  ,    
        //    , 
        //        <resume-point>)

        <suspend-coroutine>

        // 4.
        //   coroutine_handle
        // corotine_handle -    .
        //      :
        //   ( )  .

        using handle_type = std::coroutine_handle<promise_type>;
        using await_suspend_result_type =
            decltype(frame->awaitable.await_suspend(handle_type::from_promise(promise)));

        // 5.
        //   await_suspend(handle), 
        //   await_suspend   
        //       
        //       ( ). 
        //     -  .
        //   ,    

        if constexpr (std::is_void_v<await_suspend_result_type>)
        {
            //    void,
            //      
            // (     ,
            //    )
            frame->awaitable.await_suspend(handle_type::from_promise(promise));
            <return-to-caller-or-resumer>;
        }
        else if constexpr (std::is_same_v<await_suspend_result_type, bool>)
        {
            //    bool,
            //    false,      
            //     
            //  , ,   
            //   Awaitable  
            if (frame->awaitable.await_suspend(handle_type::from_promise(promise))
                <return-to-caller-or-resumer>;
        }
        else if constexpr (is_coroutine_handle_v<await_suspend_result_type>)
        {
            //    std::coroutine_handle<OtherPromise>,
            // ..     ,
            //      ,   
            //       
            //   
            auto&& other_handle = frame->awaitable.await_suspend( 
                handle_type::from_promise(promise));
            other_handle.resume();
        }
        else
        {
            static_assert(false);
        }
    }

    // 6.
    //    ()
    //   await_resume().     .
    //        co_await.
resume_point:
    return frame->awaitable.await_resume();
}


:



  1. , , co_await. , , ;
  2. , await_suspend . . , - . , await_suspend . await_suspend. await_resume, Awaitable . Promise , await_suspend. , await_suspend: (this ) Promise, .


Awaitable type-traits :



is_awaitable
//    std::coroutine_handle
template<typename Type>
struct is_coroutine_handle : std::false_type
{};

template<typename Promise>
struct is_coroutine_handle<std::coroutine_handle<Promise>> : std::true_type
{};

//      await_suspend
// - void
// - bool
// - std::coroutine_handle
template<typename Type>
struct is_valid_await_suspend_return_type : std::disjunction<
    std::is_void<Type>,
    std::is_same<Type, bool>,
    is_coroutine_handle<Type>>
{};

//  await_suspend
template<typename Type>
using is_await_suspend_method = is_valid_await_suspend_return_type<
    decltype(std::declval<Type>().await_suspend(std::declval<std::coroutine_handle<>>()))>;

//  await_ready
template<typename Type>
using is_await_ready_method = std::is_constructible<bool, decltype(
    std::declval<Type>().await_ready())>;

//   Awaitable
/*
templae<typename Type>
struct Awaitable
{
...
    bool await_ready();
    void await_suspend(std::coroutine_handle<>);
    Type await_resume();
...
}
*/
template<typename Type, typename = std::void_t<>>
struct is_awaitable : std::false_type
{};

template<typename Type>
struct is_awaitable<Type, std::void_t<
    decltype(std::declval<Type>().await_ready()),
    decltype(std::declval<Type>().await_suspend(std::declval<std::coroutine_handle<>>())),
    decltype(std::declval<Type>().await_resume())>> : std::conjunction<
    is_await_ready_method<Type>,
    is_await_suspend_method<Type>>
{};

template<typename Type>
constexpr bool is_awaitable_v = is_awaitable<Type>::value;


:



template<typename Rep, typename Period>
auto operator co_await(std::chrono::duration<Rep, Period> duration) noexcept
{
    struct Awaitable
    {
        explicit Awaitable(std::chrono::system_clock::duration duration)
            : duration_(duration)
        {}

        bool await_ready() const noexcept
        {
            return duration_.count() <= 0;
        }

        void await_resume() noexcept
        {}

        void await_suspend(std::coroutine_handle<> h)
        {
            //  timer::async      .
            //     ,   
            //     callback.
            timer::async(duration_, [h]()
            {
                h.resume();
            });
        }

    private:

        std::chrono::system_clock::duration duration_;
    };

    return Awaitable{ duration };
}

// ,         
Task tick()
{
    using namespace std::chrono_literals;

    co_await 1s;
    std::cout << "1..." << std::endl;

    co_await 1000ms;
    std::cout << "2..." << std::endl;
}

int main()
{
    tick();
    std::cin.get();
}


  1. tick;
  2. co_await Awaitable, 1 ;
  3. await_ready, ;
  4. tick, ;
  5. await_suspend ;
  6. await_suspend timer::async, callback. callback ;
  7. main;
  8. main get, , . , ;
  9. , callback, , ;
  10. resume . : tick , , ;
  11. await_resume Awaitable, co_await ;
  12. await_resume , co_await , ;
  13. tick "1...";
  14. co_await. 2. , main, a , callback, .. resumer'. ;
  15. tick ( )


co_await Awaitable, Promise .



Promise.



Awaitable, Promise , .



:



  1. . . .. ;
  2. - ( co_awat/co_yield/co_return). , .. ;
  3. , . .


//    .
//       
// 1. resume -   , 
//         ,  -.
// 2. promise -   Promise
// 3. state -  
// 4. heap_allocated -        
//           
// 5. args -   
// 6. locals -     
// ...
struct coroutine_frame
{
    void (*resume)(coroutine_frame *);
    promise_type promise;
    int16_t state;
    bool heap_allocated;
    // args
    // locals
    //...
};

// 1.     .  .
template<typename ReturnValue, typename ...Args>
ReturnValue Foo(Args&&... args)
{
    // 1.
    //   Promise
    using coroutine_traits = std::coroutine_traits<ReturnValue, Args...>;
    using promise_type = typename coroutine_traits::promise_type;

    // 2.
    //   . 
    //      
    //      Promise,     
    //  ,    ,
    //     .
    // 1.   promise_type   
    //    get_return_object_on_allocation_failure,
    //        new,   
    //          get_return_object_on_allocation_failure,
    //        .
    // 2.      new.
    coroutine_frame* frame = nullptr;
    if constexpr (has_static_get_return_object_on_allocation_failure_v<promise_type>)
    {
        frame = reinterpret_cast<coroutine_frame*>(
            operator new(__builtin_coro_size(), std::nothrow));
        if(!frame)
            return promise_type::get_return_object_on_allocation_failure();
    }
    else
    {
        frame = reinterpret_cast<coroutine_frame*>(operator new(__builtin_coro_size()));
    }

    // 3.
    //      .
    //     .
    //     (lvalue  rvalue)   .
    <move-args-to-frame>

    // 4.
    //    promise_type     
    new(&frame->promise) create_promise<promise_type>(<frame-lvalue-args>);

    // 5.
    //   Promise::get_return_object().
    //      
    //         .
    //         ,
    // ..      (.  co_await).
    auto return_object = frame->promise.get_return_object();

    // 6.
    //    -  
    //    
    //   GCC, ,    
    // ramp-fucntion (  )  
    // action-function ( -) 
    void couroutine_states(coroutine_frame*);
    couroutine_states(frame);

    // 7.
    //    , 
    //          ,
    //         
    // - couroutine_states,       .
    return return_object;
}


Promise new delete. , , new delete:



struct Promise
{
    void* operator new(std::size_t size, std::nothrow_t) noexcept
    {
        ...
    }

    void operator delete(void* ptr, std::size_t size)
    {
        ...
    }

    //        
    static auto get_return_object_on_allocation_failure() noexcept
    {
        //       
        return make_invalid_task();
    }
};


new c , . , , leading-allocator convention.



//  Promise    new c  
template<typename Allocator>
struct Promise : PromiseBase
{
    // std::allocator_arg_t -  tag-
    //       
    void* operator new(std::size_t size, std::allocator_arg_t, Allocator allocator) noexcept
    {
        ...
    }

    void operator delete(void* ptr, std::size_t size)
    {
        ...
    }
};

//     std::coroutine_traits
namespace std
{
    template<typename... Args>
    struct coroutine_traits<Task, Args...>
    {
        using promise_type = PromiseBase;
    };

    template<typename Allocator>
    struct coroutine_traits<Task, std::allocator_arg_t, Allocator>
    {
        using promise_type = Promise<Allocator>;
    };
}

//        
int main()
{
    MyAlloc alloc;
    coro(std::allocator_arg, alloc);
    ...
}


, new delete , .



. .



-, . , , , . , , , Undefined Behavior.



void Coroutine(const std::vector<int>& data)
{
    co_await 10s;
    for(const auto& value : data)
        std::cout << value << std::endl;
}

void Foo()
{
    // 1.        vector<int>;
    // 2.          data;
    // 3.      , ..  
    //    ,  ,     data 
    //          ;
    // 4.       ;
    // 5. ,       vector<int>, 
    //     ,   co_await    
    //      Foo   ;
    // 6.  10 ,        
    //       c , ,    data 
    //    ( ,    ),   .
    Coroutine({1, 2, 3});
    ...
}


Promise. , , , , Promise ( ) , , . , Promise - , , Promise, .



Promise get_return_object. , . : get_return_object . , , - , . onadic composition.



class Task
{
public:

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

    void resume()
    {
        if(coro_handle)
            coro_handle.resume();
    }

private:

    Task() = default;
    explicit Task(std::coroutine_handle<> handle)
        : coro_handle(handle)
    {}

    std::coroutine_handle<> coro_handle;
};


std::coroutine_handle — , : ( ) . from_promise, Promise.



couroutine_states -, couroutine_states , , get_return_object .



State Machine.



couroutine_states - co_awat/co_yield/co_return : , resume. .



void couroutine_states(coroutine_frame* frame)
{
    switch(frame->state)
    {
        case 0:
        ... goto resume_point_0;
        case N:
            goto resume_point_N;
        ...
    }

    co_await promise.initial_suspend();

    try
    {
        // function body
    }
    catch(...)
    {
        promise.unhandled_exception();
    }

final_suspend:
    co_await promise.final_suspend();
}


, co_await, resume_point — .



{
    ...
resume_point:
    return frame->awaitable.await_resume();
}


co_await — co_await, state, . — , , co_await .



initial_suspend co_await. : . Awaitable: std::suspend_never, std::suspend_always, initial_suspend, .



namespace std
{
    struct suspend_never
    {
        bool await_ready() noexcept { return true; }
        void await_suspend(coroutine_handle<>) noexcept {}
        void await_resume() noexcept {}
    };

    struct suspend_always
    {
        bool await_ready() noexcept { return false; }
        void await_suspend(coroutine_handle<>) noexcept {}
        void await_resume() noexcept {}
    };
}

//   ,      
//  
class Task
{
public:
    struct promise_type
    {
        ...
        auto init_suspend() const noexcept
        {
            return std::suspend_never{};
        }
    }
    ...
};

//    ,   
//     
//      ,
//         resume.
class TaskManual
{
public:
    struct promise_type
    {
        ...
        auto init_suspend() const noexcept
        {
            return std::suspend_always{};
        }
    }
    ...
};


. , try-catch unhandled_exception .



, co_await, co_yield co_return. . Promise, .



co_yield <expr> :



co_await frame->promise.yield_value(<expr>);


.. Promise . yield_value :



template<typename Type>
class Task
{
public:
    struct promise_type
    {
        ...
        // C   ,
        //    , 
        //     co_await   std::suspend_always.
        auto yield_value(Type value)
        {
            current_value = std::move(value);
            return std::suspend_always{};
        }
    };
    ...
};


Task Promise . co_yield. cppcoro



co_return return. .



  • co_rturn :



    // co_return;
    frame->promise.return_void();
    goto final_suspend;


  • , , void, co_rturn



    // co_return <expr>;
    frame->promise.return_value(<expr>);
    goto final_suspend;


  • void,



    // co_return <expr>;
    <expr>;
    frame->promise.return_void();
    goto final_suspend;




, co_return, co_return;. .. frame->promise.return_void() .



Promise return_value return_void, final_suspend.



initial_suspend, final_suspend . , .



//   ,        
//      .  Promise   ,
//   ,       
//   delete,      .
//       Undefined Behavior.
//   ,       .
class Task
{
public:
    struct promise_type
    {
        ...
        auto final_suspend() const noexcept
        {
            //     
            return std::suspend_never{};
        }
    };
    ...
};

//    ,       
//         .
//          Undefined Behavior.
//      
//    coroutine_handle::destroy()
//   ,       , 
//          Promise.
class TaskManual
{
public:
    struct promise_type
    {
        ...
        auto final_suspend() const noexcept
        {
            //    
            return std::suspend_always{};
        }
    }
    ...
};


co_await . init_suspend final_suspend, co_yield, . . Promise await_transform, co_await



// co_await <expr>
co_await frame->promise.await_transform(<expr>);


, , co_await , Awaitable, . co_await .



class Task
{
public:
    struct promise_type
    {
        ...
        template<typename Type>
        auto await_transform(Type&& Whatever) const noexcept
        {
            static_assert(false,
                "co_await is not supported in coroutines of type Generator");
            return std::suspend_never{};
        }
    };
    ...
};




:





:





:





我很高兴收到评论和建议(您可以通过电子邮件yegorov.alex@gmail.com)

谢谢!




All Articles