coroutine
스택리스(스택이 없는) 코-루틴 구현에 대한 지원을 제공한다.
class coroutine
멤버 함수
이 름 | 설 명 |
coroutine [constructor] | 초기 상태로 코-루틴을 생성한다. |
is_child | 코-루틴이 포크(fork)의 자식이면, true를 반환한다. |
is_complete | 코-루틴이 최종(terminal) 상태에 도달하면, true를 반환한다. |
is_parent | 코-루틴이 포크(fork)의 부모이면, true를 반환한다. |
coroutine 클래스는 스택리스(스택이 없는) 코-루틴을 구현하는데 사용될 수 있다. 클래스 자체는 코-루틴의 현재 상태를 저장하는데 사용된다.
coroutine은 복사-생성 및 할당 가능하며, 공간 오버헤드는 단일 int 유형이다. coroutine은 기본 클래스로 사용될 수 있고:
class session : coroutine { ... };
또는 데이터 멤버로 사용될 수 있고:
class session { ... coroutine coro_; };
또는 람다(lambda)나 bind()를 사용하여 함수 인수로 바인딩될 수도 있다. 중요한 것은 코-루틴이 살아 있는 동안 개체의 복사본을 응용프로그램이 유지한다는 것이다.
의사(pseudo)-키워드
코-루틴은 매크로로 구현된 "pseudo-keywords(의사-키워드)"와 함께 사용된다. 이러한 매크로는 헤더 파일에 의해 정의되고:
#include <boost/asio/yield.hpp>
반대로 다음과 같이 정의되지 않을 수 있다:
#include <boost/asio/unyield.hpp>
reenter
reenter 매크로는 코-루틴의 본문을 정의하는 데 사용된다. 코-루틴 개체에 대한 포인터나 참조같은 단일 인수가 필요하다. 예를 들어, 기본 클래스가 코-루틴 개체이면 다음을 작성할 수 있고:
reenter (this) { ... coroutine body ... }
데이터 멤버나 다른 변수를 작성하는 경우 다음을 작성할 수 있다:
reenter (coro_) { ... coroutine body ... }
reenter가 런타임에 실행되면, 제어가 마지막 yield나 fork 위치로 이동한다.
또한 코-루틴 본문은 다름과 같은 단일 문일 수 있다.
reenter (this) for (;;) { ... }
제한: reenter 매크로는 switch를 사용하여 구현된다. 즉, 코-루틴 본문에서 지역 변수를 사용할 때 주의해야 한다. 코-루틴을 다시 입력할 경우 변수 정의를 무시할 수 있는 위치에서는 지역 변수가 허용되지 않는다.
yield statement
이 형태의 yield 키워드는 종종 비동기식 작업과 함께 사용된다:
yield socket_->async_read_some(buffer(*buffer_), *this);
이 단계는 네가지 논리적 단계로 나뉜다:
- yield는 코-루틴의 현재 상태를 저장한다.
- 명령문은 비동기식 작업을 시작한다.
- 재시작 위치는 명령문 바로 뒤로 정의된다.
- 제어는 코-루틴 본문의 마지막으로 전달된다.
비동기식 작업이 완료되면, 함수 개체가 호출되고 reenter는 제어를 재시작 위치로 전달한다. 비동기식 작업을 통해 코-루틴 상태를 앞으로 전달하는 것이 중요하다. 위의 코드 조각에서 현재 클래스는 기본 클래스나 데이터 멤버로 coroutine 개체를 가진 함수 개체이다.
이 명령문은 복합 문일 수도 있으며, 이를 통해 범위가 제한된 로컬 변수를 정의할 수 있다:
yield { mutable_buffers_1 b = buffer(*buffer_); socket_->async_read_some(b, *this); }
yield return expression ;
이 형태의 yield는 생성기(generator)나 코-루틴 기반 파서에 종종 사용된다. 예를 들어, 함수 개체를 다음과 같이 정의한다:
struct interleave : coroutine { istream& is1; istream& is2; char operator()(char c) { reenter (this) for (;;) { yield return is1.get(); yield return is2.get(); } } };
두 입력 스트림에서 문자를 상호 배치하는 간단한 코-루틴을 정의한다.
이 유형의 yield는 다음과 같은 세가지 논리적 단계로 나뉜다:
- yield는 코-루틴의 현재 상태를 저장한다.
- 재시작 위치는 세미콜론 바로 뒤로 정의된다.
- 표현식 값은 함수에서 반환된다.
yield ;
이 형태의 yield는 다음 단계와 동일하다:
- yield는 코-루틴의 현재 상태를 저장한다.
- 재시작 위치는 세미콜론 바로 뒤로 정의된다.
- 제어는 코-루틴 본문의 마지막으로 전달된다.
코-루틴이 협동 스레드에서 사용되거나 스케줄이 명시적으로 관리되도록 이 형태를 적용할 수 있다. 예를 들어:
struct task : coroutine { ... void operator()() { reenter (this) { while (... not finished ...) { ... do something ... yield; ... do some more ... yield; } } } ... }; ... task t1, t2; for (;;) { t1(); t2(); }
yield break ;
yield의 마지막 형태는 코-루틴을 명시적으로 종료하는 데 사용된다. 이 형태는 다음 두 단계로 구성된다:
- yield는 종료를 나타내기 위해 코-루틴 상태를 설정한다.
- 제어는 코-루틴 본문의 마지막으로 전달된다.
한번 종료되면, is_complete() 호출은 true를 반환하고 코-루틴은 다시 진입할 수 없다.
코-루틴 본문이 yield 없이 종료되면(예를 들어 반환하거나 예외가 발생하거나 본문의 마지막까지 실행이 되어서), 코-루틴은 암시적으로 종료될 수도 있다.
fork statement
fork 의사-키워드는 코-루틴을 "forking(포크)"할 때, 즉 두 개 이상의 복사본으로 분리할 때 사용된다. fork의 한 가지 사용 예는, 각 클라이언트 연결을 처리하기 위해 새 코-루틴을 생성하는 서버가 있다.
reenter (this) { do { socket_.reset(new tcp::socket(my_context_)); yield acceptor->async_accept(*socket_, *this); fork server(*this)(); } while (is_parent()); ... client-specific handling follows ... }
fork와 관련된 논리적 단계는 다음과 같다:
- fork는 코-루틴의 현재 상태를 저장한다.
- 명령문은 코-루틴의 복사본을 생성하고, 즉시 실행하거나 나중에 실행되도록 스케줄한다.
- 재시작 위치는 세미콜론 바로 뒤로 정의된다.
- "parent(부모)"의 경우, 제어는 즉시 다음 행에서 계속된다.
is_parent()와 is_child() 함수는 부모와 자식을 구분하는 데 사용될 수 있다. 이런 함수를 사용하여 이후의 제어 흐름을 변경할 수 있다.
fork는 실제로 "forking(포크)" 자체를 하지는 않는다. 코-루틴의 clone(복제본)을 생성하고 호출하는 것은 응용프로그램의 책임이다. clone(복제본)은 위와 같이 즉시 호출되거나 post 같은 것을 사용하여 지연된 실행으로 스케줄될 수 있다.
대체 매크로 이름
원하는 경우, 응용프로그램은 의사-키워드를 대신하여 보다 일반적인 명명 규칙을 따르는 매크로 이름을 사용할 수 있다. 다음과 같다:
- reenter를 대신하여 BOOST_ASIO_CORO_REENTER
- yield를 대신하여 BOOST_ASIO_CORO_YIELD
- fork를 대신하여 BOOST_ASIO_CORO_FORK
요구 사항
일반 헤더 : boost/asio/coroutine.hpp
편의 헤더 : boost/asio.hpp
Boost.Asio 홈
'Boost C++ Libraries > Boost.Asio' 카테고리의 다른 글
Boost.Asio - Completion handler requirements (0) | 2021.03.22 |
---|---|
Boost.Asio - basic_yield_context (0) | 2021.03.22 |
Boost.Asio - use_awaitable_t::executor_with_default (0) | 2021.03.22 |
Boost.Asio - use_awaitable_t (0) | 2021.03.22 |
Boost.Asio - associated_allocator (0) | 2021.03.21 |