Boost C++ Libraries/Boost.Asio

Boost.Asio - coroutine

까마귀75 2021. 3. 22. 14:05
728x90
반응형

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가 런타임에 실행되면, 제어가 마지막 yieldfork 위치로 이동한다.

또한 코-루틴 본문은 다름과 같은 단일 문일 수 있다.

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 홈

728x90
반응형