C++20 리서치 - Coroutine[8]
C++20에 추가된 coroutine은 기존 sub-routine과 다르게 caller 함수와 협력 관계로 동작합니다.
Coroutine 개념 및 특징
- co-routine은 호출 중간에 작업을 중단 하고 호출 위치로 복귀 이후, 중단 시점에서 다시 작업 재개가 가능합니다.
- Heap에 coroutine의 Stack frame을 보관하여 복귀 시점을 찾는 등의 동작으로 구현 되어 있습니다.
- C++20에서는 다른 언어의 co-routine 보다 사용이 다소 복잡하지만, 유연성을 제공합니다.
- single thread로 동작 할 수도 있고, multi thread로도 동작 할 수 있습니다.
- 신규 헤더 : coroutine
- 신규 키워드 : co_await, co_yield
기본 예제
#include <iostream>
#include <coroutine>
#include <Windows.h>
// Coroutine을 사용하기 위한 최소한의 구현 객체 Coroutine framework
template<typename T>
class Generator
{
public:
struct Promise
{
private:
T value;
public:
T Getvalue() { return value; }
Generator get_return_object()
{
return Generator{ std::coroutine_handle<Promise>::from_promise(*this) };
}
auto yield_value(int n)
{
value = n;
return std::suspend_always{};
}
auto initial_suspend() { return std::suspend_always{}; }
auto return_void() { return std::suspend_never{}; }
auto final_suspend() { return std::suspend_always{}; }
auto unhandled_exception() { return exit(1); }
};
using promise_type = Promise;
std::coroutine_handle<promise_type> coro;
Generator( std::coroutine_handle<promise_type> c ) : coro(c) {}
~Generator() { if ( coro ) coro.destroy(); }
};
Generator f()
{
std::cout << "Run 1" << std::endl;
Sleep(10000);
//co_await std::suspend_always {}; // 작업 중단 시점 명시, suspend_always -> awaitable object
co_yiled 10;
std::cout << "Run 2" << std::endl;
co_yield 20;
std::cout << "Run 3" << std::endl;
}
int main()
{
Generator<int> g = foo();
std::cout << "main1" << std::endl;
while(true)
{
g.coro.resume();
// 코루틴이 작업중인지 확인 가능
if( g.coro.done() ) break;
}
std::cout << g.coro.promise().getvalue() << std::endl; // 코루틴과 값 주고 받기
std::cout << "main2" << std::endl;
g.coro.resume();
std::cout << g.coro.promise().getvalue() << std::endl;
std::cout << "main3" << std::endl;
}
Awatible object (멀티 스레딩)
#include <iostream>
#include <coroutine>
#include <Windows.h>
// Coroutine을 사용하기 위한 최소한의 구현 객체 Coroutine framework
template<typename T>
class Generator
{
public:
struct Promise
{
private:
T value;
public:
T Getvalue() { return value; }
Generator get_return_object()
{
return Generator{ std::coroutine_handle<Promise>::from_promise(*this) };
}
auto yield_value(int n)
{
value = n;
return std::suspend_always{};
}
auto initial_suspend() { return std::suspend_always{}; }
auto return_void() { return std::suspend_never{}; }
auto final_suspend() { return std::suspend_always{}; }
auto unhandled_exception() { return exit(1); }
};
using promise_type = Promise;
std::coroutine_handle<promise_type> coro;
Generator( std::coroutine_handle<promise_type> c ) : coro(c) {}
~Generator() { if ( coro ) coro.destroy(); }
};
struct resume_new_thread
{
void await_suspend(std::coroutine_handle<> handle)
{
std::thread t([handle]() { handle(); });
t.detach();
}
constexpr bool await_read() const noexcept { return false; }
constexpr bool await_resume() const noexcept { }
}
Generator f()
{
std::cout << "Run 1 : " << std::this_thread::get_id() << std::endl;
co_wait resume_new_thread{}; //여기에 걸리면 새로운 thread를 만들어라
std::cout << "Run 2 : " << std::this_thread::get_id() << std::endl;
}
int main()
{
Generator<int> g = foo();
g.coro.resume(); // resume시 새로운 스레드가 생겨서 Run 2 실행(멀티스레딩)
std::cout << "main" << std::endl;
int n;
std::cin >> n;
}