この記事では、コルチンの概念とその分類を詳細に分析し、新しいC ++ 20標準によって提供される実装、仮定、およびトレードオフについて詳しく見ていきます。
一般情報
ルーチンは、それらに対して実行される操作の観点から、ルーチン(関数)の概念を一般化したものと見なすことができます。コルーチンとサブルーチンの基本的な違いは、コルーチンは、ローカルデータ(実行状態)を維持しながら、追加の操作を使用して、実行を明示的に一時停止し、他のプログラムユニットに制御を与え、制御が回復した同じポイントで作業を再開する機能を提供することです。連続する呼び出しの間に、より柔軟で拡張された制御フローを提供します。
この定義とさらなる推論を明確にし、補助的な概念と用語を紹介するために、C ++の通常の関数のメカニズムとそのスタックの性質を検討してください。
2つの操作のコンテキストで関数のセマンティクスを検討します。
(call). . :
- — (activation record, activation frame), ;
- ( ) , ;
- . ;
- — , .
, .
(return). . :
- ( ) ;
- , ;
- .
, . ().
:
- (strictly nested lifetime) . , : . .
- .
, . — , : 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 |
+----------------+
, , .
, .
- ;
- ;
- ( ).
(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 .
, , , . , , , , . , , , , .. . .
:
- (top level function), ;
- ;
- , , ;
- ( ), , .
, 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();
}
:
- , , co_await. , , ;
- ,
await_suspend
. . , - . ,await_suspend
.await_suspend
.await_resume
, Awaitable . Promise ,await_suspend
. ,await_suspend
: (this ) Promise, .
Awaitable type-traits :
// 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();
}
-
tick
; - co_await Awaitable, 1 ;
-
await_ready
, ; -
tick
, ; -
await_suspend
; - await_suspend
timer::async
,callback
.callback
; - —
main
; -
main
get
, , . , ; - ,
callback
, , ; -
resume
. :tick
, , ; -
await_resume
Awaitable, co_await ; -
await_resume
, co_await , ; -
tick
"1..."; - co_await. 2. ,
main
, a ,callback
, .. resumer'. ; -
tick
( )
co_await Awaitable, Promise .
Promise.
Awaitable, Promise , .
:
- . . .. ;
- - (
co_awat
/co_yield
/co_return
). , .. ; - , . .
// .
//
// 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{};
}
};
...
};
:
:
:
- Working Draft, Standard for Programming Language C++ (N4830);
- Coroutine TS (N4760);
- First-class symmetric coroutines in C++;
- Core Coroutines;
- Low-level API for stackful coroutines;
- A low-level API for stackful context switching;
- Coroutines: Use-cases and Trade-offs;
- Revisiting Coroutines;
- スタックフルコロチンとスタックレス再開可能関数;
- 再開可能な機能;
- 再開可能な式;
- LLVMのCoroutines ;
- ブーストCoroutine2 ;
コメントや提案をいただければ幸いです(yegorov.alex@gmail.comにメールを送信できます)
ありがとうございます。