15 분 소요

1. Overview

  • 부스트 파이버는 userland thread(fiber)를 위한 framework을 제공
  • API에는 표준 스레드와 유사하게 파이버를 관리하고 동기화 하는 클래스와 기능이 포함되어 있음
  • 각각의 파이버는 자체 스택을 가짐
  • 파이버는 현재 실행 상태를 저장(registers, CPU flags, instruction pointer, and stack pointer)하고 나중에 이 상태를 복원할 수 있음
  • 아이디어는 협력 스케줄링(cooperative scheduing)을 사용하여 싱글 스레드에서 여러 실행 경로를 실행하는 것 ( 스레드는 선점적으로 스케줄링 )
  • 실행 중인 파이버는 다른 파이버를 실행할 수 있도록 양보해야 하는 시기를 명시적으로 결정(context switch)
  • 파이버는 내부적으로 boost.context의 call/cc를 사용하여 해당 실행 컨텍스트를 관리, 예약하고 필요한 경우 동기화를 해줌
    • 스레드 간의 컨텍스트 스위치 : 수천 CPU cycles의 비용이 듬
    • 파이버 간의 컨텍스트 스위치 : 일반적으로 100 cycles 미만
  • 파이버는 언제든지 단일 스레드에서 실행

  • call/cc 란?
    • call with current continuation 로 universal control operator
    • 현재 CONTINUATION을 first-class object로 갭쳐하고 이를 다른 continuation에 argument로 전달하는 연산자
    • CONTINUATION은 주어진 시점에서 control flow의 state를 나타냄
    • CONTINUATION은 control flow를 변경하기 위해 suspended and resumed 가 가능
      namespace ctx=boost::context;
      int a;
      ctx::continuation source=ctx::callcc(
          [&a](ctx::continuation && sink){
              a=0;
              int b=1;
              for(;;){
                  sink=sink.resume();
                  int next=a+b;
                  a=b;
                  b=next;
              }
              return std::move(sink);
          });
      for (int j=0;j<10;++j) {
          std::cout << a << " ";
          source=source.resume();
      }
    
      output:
          0 1 1 2 3 5 8 13 21 34
    
  • 파이버 사용을 위한 namespace

      #include<boost/fiber/all.hpp>
      namespaceboost::fibers
      namespaceboost::this_fiber
    

1.1. Fibers and Threads

  • Control은 주어진 스레드에서 시작되는 fibers 간에 협력적으로 전달, 스레드에서 적어도 하나의 파이버가 running

  • Thread
    • 스레드에서 추가 파이버를 생성하는 것은 프로그램이 실행 중인 코어를 더 효과적으로 사용할 수 있지만 더 많은 하드웨어 코어에 프로그램을 배포하지 않음
  • Fiber
    • 파이버는 동일한 스레드에서 다른 파이버의 concurrent access에 대해 해당 리소스를 명시적으로 방어할 필요가 없이 부모 스레드가 독점적으로 소유한 모든 리소스에 안전하게 access 가능
    • 해당 스레드의 다른 파이버가 해당 리소스에 동시에 접근하지 않는다는 것이 이미 보장, 이는 레거시 코드에 concurrency(동시성)을 주입할 때 특히 중요
    • asynchronous I/O 를 사용하여 레거시 코드를 실행하는 fiber를 안전하게 생성할 수 있음
    • 특징
      • 파이버는 asynchronous I/O 를 기반으로 concurrent 코드를 구성하는 방법을 제공
      • 파이버에서 실행되는 코드는 blocking function call 처럼 보이는 것을 만들 수 있음, 해당 call은 실행 중인 파이버를 값싼 비용으로 일시 중단하여 동일한 스레드의 다른 파이버가 실행되도록 할 수 있음
      • 작업이 완료되면 상태를 명시적으로 저장하거나 복원할 필요 없이 일시 중단된 파이버가 다시 시작됨
      • 해당 local stack 변수는 call 간 에서 지속됨
      • 파이버는 한 스레드에서 다른 스레드로 마이그레이션할 수 있지만 라이브러리는 기본적으로 이 작업을 수행하지 않음
      • 스레드 간에 파이버를 마이그레이션하는 사용자 지정 스케줄러를 제공하여 허용되는 파이버를 결정하는 데 도움이 되도록 파이버 속성을 지정할 수 있음
      • 부스트 파이버는 multiple cores에서 multiplex fibers를 할 수 있음
      • 특정 스레드에서 시작된 파이버는 마이그레이션되지 않는 한 해당 스레드에서 계속 실행
      • 다른 스레드에 의해 unblocked 될 수 있지만 현재 스레드에서 파이버를 blocked 에서 ready로 바꾸는 것임

1.2. thread-local storage

  • 파이버는 스레드 로컬 스토리지에 액세스할 수 있음
  • 이는 동일한 스레드에서 실행되는 모든 파이버 간에 공유
  • [fiber_specific_ptr](https://www.boost.org/doc/libs/1_80_0/libs/fiber/doc/html/fiber/fls.html#class_fiber_specific_ptr)을 참조

1.3. BOOST_FIBERS_NO_ATOMICS

  • 부스트 파이버 라이브러리에서 제공하는 Fiber synchronization 객체는 기본적으로 서로 다른 스레드에서 실행되는 Fiber를 안전하게 동기화시킴
  • 그러나, BOOST_FIBERS_NO_ATOMICS가 정의된 라이브러리를 빌드하면 성능을 위해 해당 레벨의 동기화를 제거할 수 있음
  • 해당 매크로로 구축되면 특정 동기화 객체를 참조하는 모든 파이버가 동일한 스레드에서 실행되고 있는지 확인이 필요 Synchronization.

1.4. Blocking

  • 특정 파이버가 block된다고 기술하면 제어권을 양보하여 동일한 스레드의 다른 파이버가 실행될 수 있음을 의미
  • 부스트 파이버에서 제공하는 동기화 메커니즘
    • 파이버는 일반적인 스레드 동기화 메커니즘을 사용할 수 있음
    • 그러나 이러한 메커니즘을 호출하는 파이버는 전체 스레드를 block 하여 그 동안 다른 파이버가 해당 스레드에서 실행되는 것을 방지
    • 예를 들어 파이버가 동일한 스레드에서 다른 파이버의 값을 기다리고자 할 때 std::future를 사용하는 것은 안좋음
    • std::future::get()은 전체 스레드를 차단하여 다른 파이버가 전달하는 것을 방지, 대신 [future<>](https://www.boost.org/doc/libs/1_80_0/libs/fiber/doc/html/fiber/synchronization/futures/future.html#class_future)을 사용해야 함
    • 또한 일반 blocking I/O 작업을 호출하는 파이버는 전체 스레드를 차단함
    • 파이버를 사용할 때는 비동기 I/O를 일관되게 사용하는 것이 좋음
    • Boost.Asio 및 기타 비동기 I/O 작업은 적용 가능 Integrating Fibers with Asynchronous Callbacks.
    • 부스트 파이버는 부스트 컨텍스트(Boost.Context)에 의존!

2. Fiber management

2.1. 개요

#include <boost/fiber/all.hpp>

namespace boost {
namespace fibers {

class fiber;
bool operator<( fiber const& l, fiber const& r) noexcept;
void swap( fiber & l, fiber & r) noexcept;

template< typename SchedAlgo, typename ... Args >
void use_scheduling_algorithm( Args && ... args);
bool has_ready_fibers();

namespace algo {

struct algorithm;
template< typename PROPS >
struct algorithm_with_properties;
class round_robin;
class shared_round_robin;

}}

namespace this_fiber {

fibers::id get_id() noexcept;
void yield();
template< typename Clock, typename Duration >
void sleep_until( std::chrono::time_point< Clock, Duration > const& abs_time)
template< typename Rep, typename Period >
void sleep_for( std::chrono::duration< Rep, Period > const& rel_time);
template< typename PROPS >
PROPS & properties();

}

2.2. 사용

  • 각 fiber는 스케줄러에 의해 시작 및 관리됨
  • fiber object는 move-only
boost::fibers::fiber f1; // not-a-fiber

void f() {
    boost::fibers::fiber f2( some_fn);

    f1 = std::move( f2); // f2 moved to f1
}

2.3 Class fiber

#include <boost/fiber/fiber.hpp>

namespace boost {
namespace fibers {

class fiber {
public:
    // class id는 아래에서 알아보기!
    class id;

    // default 생성자
    constexpr fiber() noexcept;

    // 생성자들, Fn은 copyable or movable
    template< typename Fn, typename ... Args >
    fiber( Fn &&, Args && ...);

    template< typename Fn, typename ... Args >
    fiber( launch, Fn &&, Args && ...);

    template< typename StackAllocator, typename Fn, typename ... Args >
    fiber( std::allocator_arg_t, StackAllocator &&, Fn &&, Args && ...);

    template< typename StackAllocator, typename Fn, typename ... Args >
    fiber( launch, std::allocator_arg_t, StackAllocator &&, Fn &&, Args && ...);

    ~fiber();

    fiber( fiber const&) = delete;

    fiber & operator=( fiber const&) = delete;

    fiber( fiber &&) noexcept;

    fiber & operator=( fiber &&) noexcept;

    void swap( fiber &) noexcept;

    // 파이버가 joinable인 동안 소멸자가 실행되지 않도록 해야 됨
    // 파이버가 완료된 것을 알고 있더라도 파이버를 제거하기 전에 join() or detach()를 호출해야 됨
    bool joinable() const noexcept;

    id get_id() const noexcept;

    // 실행중인 파이버가 detach되고 더 이상 관련된 파이버가 없음
    void detach();

    // 실행중인 파이버가 완료될 때까지 기다림
    void join();

    // *this에 대한 스케줄러 속성 인스턴스에 대한 참조
    template< typename PROPS >
    PROPS & properties();
};

bool operator<( fiber const&, fiber const&) noexcept;

void swap( fiber &, fiber &) noexcept;

template< typename SchedAlgo, typename ... Args >
void use_scheduling_algorithm( Args && ...) noexcept;

// 스케줄러에 실행할 파이버가 있으면 true
bool has_ready_fibers() noexcept;

}}

2.4. Class fiber::id

#include <boost/fiber/fiber.hpp>

namespace boost {
namespace fibers {

class id {
public:
    constexpr id() noexcept;
    bool operator==( id const&) const noexcept;
    bool operator!=( id const&) const noexcept;
    bool operator<( id const&) const noexcept;
    bool operator>( id const&) const noexcept;
    bool operator<=( id const&) const noexcept;
    bool operator>=( id const&) const noexcept;

    template< typename charT, class traitsT >
    friend std::basic_ostream< charT, traitsT > &
    operator<<( std::basic_ostream< charT, traitsT > &, id const&);
};

}}

2.5. Namespace this_fiber

namespace boost {
namespace this_fiber {

fibers::fiber::id get_id() noexcept;
void yield() noexcept;
template< typename Clock, typename Duration >
void sleep_until( std::chrono::time_point< Clock, Duration > const&);
template< typename Rep, typename Period >
void sleep_for( std::chrono::duration< Rep, Period > const&);
template< typename PROPS >
PROPS & properties();

}}
  • Non-member functions
#include <boost/fiber/operations.hpp>

namespace boost {
namespace fibers {

// 현재 실행중인 파이버 id를 나타냄
fiber::id get_id() noexcept;

// abs_time에 도달할 때까지 현재 파이버를 일시 중단
template< typename Clock, typename Duration >
void sleep_until( std::chrono::time_point< Clock, Duration > const& abs_time);

// rel_time에서 지정한 시간이 경과할 때까지 현재 파이버를 일시 중단
template< class Rep, class Period >
void sleep_for( std::chrono::duration< Rep, Period > const& rel_time);
}}

// 실행 제어를 포기하고 다른 파이버가 실행되도록 허용
void yield() noexcept;

// 현재 실행 중인 파이버에 대한 스케줄러 속성 인스턴스에 대한 참조
template< typename PROPS >
PROPS & properties();

3. 스케줄링

  • 스레드 파이버는 fiber manager에 의해 조정
  • 파이버는 협력적으로 제워권을 교환하기 때문에, 현재 실행 중인 파이버는 제어권을 manager에게 전달하는 작업이 호출될 때까지 제어권을 유지
  • 파이버가 일시 중지 또는 yield 될 대마다 manger는 다음에 실행될 파이버를 결정하기 위해 스케줄러를 참조
  • 각 스레드에는 자체 스케줄러가 있으며, 프로세스의 다른 스레드는 다른 스케줄러를 사용할 수 있음
  • 기본적으로 round robin을 각 스레드의 스케줄러로 만들며 교체가 가능

3.1. use_scheduling_algorithm

  • 알고리즘 sub class는 use_scheduling_algorithm 을 호출하여 특정 스레드에 참여하는 것이 가능
void thread_fn() {
    boost::fibers::use_scheduling_algorithm< my_fiber_scheduler >();
    ...
}

3.2. 스케줄러 클래스

  • 스케줄러 클래스는 인터페이스 알고리즘을 구현해야 함
  • 파이버는 round_robin, work_stealing, numa::work_stealing 및 shared_work와 같은 스케줄러를 제공
void thread( std::uint32_t thread_count) {
    // thread registers itself at work-stealing scheduler
    boost::fibers::use_scheduling_algorithm< boost::fibers::algo::work_stealing >( thread_count);
    ...
}

// count of logical cpus
std::uint32_t thread_count = std::thread::hardware_concurrency();
// start worker-threads first
std::vector< std::thread > threads;
for ( std::uint32_t i = 1 /* count start-thread */; i < thread_count; ++i) {
    // spawn thread
    threads.emplace_back( thread, thread_count);
}
// start-thread registers itself at work-stealing scheduler
boost::fibers::use_scheduling_algorithm< boost::fibers::algo::work_stealing >( thread_count);
...

3.3. Class algorithm

#include <boost/fiber/algo/algorithm.hpp>

namespace boost {
namespace fibers {
namespace algo {

struct algorithm {
    virtual ~algorithm();

    // 파이버 f가 실행될 준비가 되었음을 스케줄러에 알림
    virtual void awakened( context *) noexcept = 0;

    // 다음에 재개할 파이버 or 준비된 파이버가 없으면 nullptr
    virtual context * pick_next() noexcept = 0;

    // 스케줄러에 실행할 파이버가 있으면 true
    virtual bool has_ready_fibers() const noexcept = 0;

    // abs_time까지 파이버가 준비되지 않음을 스케줄러에 알림
    virtual void suspend_until( std::chrono::steady_clock::time_point const&) noexcept = 0;

    // suspend_until() 에 대한 대기 중인 call 에서 return 하도록 스케줄러에 요청
    virtual void notify() noexcept = 0;
};

}}}
  • class round_robin
#include <boost/fiber/algo/round_robin.hpp>

namespace boost {
namespace fibers {
namespace algo {

class round_robin : public algorithm {
    // 파이버 f를 ready queue에 넣음
    virtual void awakened( context *) noexcept;

    // ready queue의 head에 있는 파이버 or empty인 경우 nullptr
    virtual context * pick_next() noexcept;

    // 스케줄러에 실행할 파이버가 있으면 true
    virtual bool has_ready_fibers() const noexcept;

    // abs_time까지 사용할 수 있는 파이버가 없음을 알림
    virtual void suspend_until( std::chrono::steady_clock::time_point const&) noexcept;

    // suspend_util()에 대한 대기 중인 call을 깨우면 일부 파이버가 준비되었을 수 있음
    // 이 구현은 std::condition_variable::notify_all()을 통해 suspend_until()을 꺠움
    virtual void notify() noexcept;
};

}}}
  • class work_stealing
    • local ready-queue에 준비된 파이버가 부족하면 다른 스케줄러에서 준비된 파이버를 훔침
    • 희생 스케줄러(준비된 파이버를 뺏김)는 무작위로 선택
#include <boost/fiber/algo/work_stealing.hpp>

namespace boost {
namespace fibers {
namespace algo {

class work_stealing : public algorithm {
public:
    work_stealing( std::uint32_t thread_count, bool suspend = false);

    work_stealing( work_stealing const&) = delete;
    work_stealing( work_stealing &&) = delete;

    work_stealing & operator=( work_stealing const&) = delete;
    work_stealing & operator=( work_stealing &&) = delete;

    // 파이버 f를 shared ready queue에 넣음
    virtual void awakened( context *) noexcept;

    // ready queue의 head에 있는 파이버 또는 empty면 nullptr
    virtual context * pick_next() noexcept;

    // 스케줄러에 실행할 파이버가 있으면 true
    virtual bool has_ready_fibers() const noexcept;

    // abs_time까지 사용할 수 있는 준비된 파이버가 없음을 알림
    virtual void suspend_until( std::chrono::steady_clock::time_point const&) noexcept;

    // suspend_util()에 대한 대기 중인 call을 깨우면 일부 파이버가 준비되었을 수 있음
    // 이 구현은 std::condition_variable::notify_all()을 통해 suspend_until()을 꺠움
    virtual void notify() noexcept;
};

}}}
  • Class shared_work
    • round_robin 방식으로 파이버를 스케줄링하는 알고리즘
    • 준비된 파이버는 shared_work의 모든 instance(서로 다른 스레드에서 실행) 간에 공유되므로 작업이 모든 스레드에 균등하게 분배
#include <boost/fiber/algo/shared_work.hpp>

namespace boost {
namespace fibers {
namespace algo {

class shared_work : public algorithm {
    shared_work();
    shared_work( bool suspend);

    // 파이버 f를 shared ready queue에 넣음
    virtual void awakened( context *) noexcept;

    // ready queue의 head에 있는 파이버 또는 empty면 nullptr
    virtual context * pick_next() noexcept;

    // 스케줄러에 실행할 파이버가 있으면 true
    virtual bool has_ready_fibers() const noexcept;

    // abs_time까지 사용할 수 있는 준비된 파이버가 없음을 알림
    virtual void suspend_until( std::chrono::steady_clock::time_point const&) noexcept;

    // suspend_util()에 대한 대기 중인 call을 깨우면 일부 파이버가 준비되었을 수 있음
    // 이 구현은 std::condition_variable::notify_all()을 통해 suspend_until()을 꺠움
    virtual void notify() noexcept;
};

}}}
  • Class context
    • 알고리즘 구현은 전달된 context instance를 관리하려는 모든 컨테이너를 사용할 수 있음
    • ready_queue_t는 일반적인 STL 컨테이너의 일부 오버헤드를 방지함
#include <boost/fiber/context.hpp>

namespace boost {
namespace fibers {

enum class type {
  none               = unspecified,
  main_context       = unspecified, // fiber associated with thread's stack
  dispatcher_context = unspecified, // special fiber for maintenance operations
  worker_context     = unspecified, // fiber not special to the library
  pinned_context     = unspecified  // fiber must not be migrated to another thread
};

class context {
public:
    class id;

    // 현재 파이버에 대한 포인터
    static context * active() noexcept;

    context( context const&) = delete;
    context & operator=( context const&) = delete;

    // *this 가 실행 파이버를 참조하는 경우 해당 파이버를 나타내는 id의 instance
    // 그렇지 않으면 기본 구성된 id를 반환
    id get_id() const noexcept;

    // *this를 실행하는 스케줄러에서 *this 파이버를 분리함
    void detach() noexcept;

    // *this를 실행하는 스케줄러에 파이버 f를 연결
    void attach( context *) noexcept;

    // *this가 지정된 type인 경우 true
    bool is_context( type) const noexcept;

    // *this 가 더이상 유효하지 않은 경우 true
    bool is_terminated() const noexcept;

    // *this가 알고리즘 구현의 ready-queue에 저장되어 있으면 true
    bool ready_is_linked() const noexcept;

    // *this가 fiber manager의 remote-ready-queue에 있으면 true
    bool remote_ready_is_linked() const noexcept;

    // *this 가 일부 동기화 object의 wait-queue에 있으면 true
    bool wait_is_linked() const noexcept;

    // *this를 ready-queue list에 저장
    template< typename List >
    void ready_link( List &) noexcept;

    // *this를 remote-ready-queue list에 저장
    template< typename List >
    void remote_ready_link( List &) noexcept;

    // *this를 wait-queue list에 저장
    template< typename List >
    void wait_link( List &) noexcept;

    // ready-queue에서 *this를 제거
    void ready_unlink() noexcept;

    // remote-ready-queue에서 *this를 제거
    void remote_ready_unlink() noexcept;

    // wait-queue에서 *this를 제거
    void wait_unlink() noexcept;

    // 다른 파이버가 this를 context::schedule()로 전달할 때까지 실행 중인 파이버를 일시 중단
    // *this는 not-ready로 표시되며 실행할 다른 파이버를 선택하기 위해 제어를 스케줄러로 전달
    void suspend() noexcept;

    // context *ctx와 연결된 파이버를 실행할 준비가 된 것으로 표시
    // 이것은 해당 파이버를 즉시 시작하지는 않음
    // 후속 재개를 위해 파이버를 스케줄러로 전달
    void schedule( context *) noexcept;
};

bool operator<( context const& l, context const& r) noexcept;

}}

4. Synchronization

  • 일반적으로, 파이버 synchronization object는 이동하거나 복사할 수 없음
  • synchronization object는 서로 다른 파이버 간에 mutually-agreed rendezvous point 역할을 함
  • object가 다른 곳에 복사된 경우 새로운 복사본에 대한 소비자가 없게 동작
  • 파이버 synchronization object는 기본적으로 서로 다른 스레드에서 실행되는 파이버를 안전하게 동기화 시킴
  • 성능을 위해 BOOST_FIBERS_NO_ATOMICS가 정의된 library를 빌드하면 위의 동기화를 제거할 수 있음

4.1. Mutex Types

  • Class mutex
    • mutex는 독점 소유권(exclusive-ownership)을 제공
    • mutex instance는 최대 하나의 파이버를 언제든지 lock 을 소유할 수 있음
    • lock(), try_lock(), unlock()에 대한 multiple concurrent 호출을 허용
#include <boost/fiber/mutex.hpp>

namespace boost {
namespace fibers {

class mutex {
public:
    mutex();
    ~mutex();

    mutex( mutex const& other) = delete;
    mutex & operator=( mutex const& other) = delete;

    // 소유권을 얻을 떄 까지 현재 파이버는 block
    void lock();
    // 현재 파이버를 block하지 않고 소유권을 얻으려고 시도
    bool try_lock();
    // 현재 파이버에 의해 *this에 대한 lock을 release
    void unlock();
};

}}
  • Class timed_mutex
    • mutex와 같이 독점 소유권을 제공 및 최대 하나의 파이버를 언제든지 잠금을 소유할 수 있음
    • lock(), try_lock(), try_lock_until(), try_lock_for() and unlock() 에 대한 다중 동시 호출 허용
#include <boost/fiber/timed_mutex.hpp>

namespace boost {
namespace fibers {

class timed_mutex {
public:
    timed_mutex();
    ~timed_mutex();

    timed_mutex( timed_mutex const& other) = delete;
    timed_mutex & operator=( timed_mutex const& other) = delete;

    // 소유권을 얻을 떄 까지 현재 파이버는 block
    void lock();

    // 현재 파이버를 block하지 않고 소유권을 얻으려고 시도
    bool try_lock();

    // 현재 파이버에 의해 *this에 대한 lock을 release
    void unlock();

    // 현재 파이버에 대한 소유권을 얻으려고 시도
    // 소유권을 얻을 수 있을 때까지 or 지정된 시간에 도달할 떄까지 block, 지정된 시간이 경과한 경우 timed_mutex::try_lock()으로 동작
    template< typename Clock, typename Duration >
    bool try_lock_until( std::chrono::time_point< Clock, Duration > const& timeout_time);
    // 현재 파이버에 대한 소유권을 얻으려고 시도
    // 소유권을 얻을 수 있을 때까지 or 지정된 시간에 도달할 떄까지 block, 지정된 시간이 경과한 경우 timed_mutex::try_lock()으로 동작
    template< typename Rep, typename Period >
    bool try_lock_for( std::chrono::duration< Rep, Period > const& timeout_duration);
};

}}
  • Class recursive_mutex
    • exclusive-ownership recursive mutex를 제공
    • lock(), try_lock(), unlock()에 대한 다중 동시 호출을 허용
    • 이미 recursive_mutex 소유권을 가지고 있는 파이버는 lock() or try_lock()을 호출하여 추가적인 레벨의 소유권을 획득할 수 있음
    • 다른 파이버에서 소유권을 획득하기 전에 단일 파이버에서 획득한 각 소유권 레벨에 대해 unlcok()을 한번 호출해야 됨
#include <boost/fiber/recursive_mutex.hpp>

namespace boost {
namespace fibers {

class recursive_mutex {
public:
    recursive_mutex();
    ~recursive_mutex();

    recursive_mutex( recursive_mutex const& other) = delete;
    recursive_mutex & operator=( recursive_mutex const& other) = delete;

    // mutex와 동일
    void lock();
    bool try_lock() noexcept;
    void unlock();
};

}}
  • Class recursive_timed_mutex
#include <boost/fiber/recursive_timed_mutex.hpp>

namespace boost {
namespace fibers {

class recursive_timed_mutex {
public:
    recursive_timed_mutex();
    ~recursive_timed_mutex();

    recursive_timed_mutex( recursive_timed_mutex const& other) = delete;
    recursive_timed_mutex & operator=( recursive_timed_mutex const& other) = delete;

    // timed_mutex와 동일
    void lock();
    bool try_lock() noexcept;
    void unlock();

    template< typename Clock, typename Duration >
    bool try_lock_until( std::chrono::time_point< Clock, Duration > const& timeout_time);
    template< typename Rep, typename Period >
    bool try_lock_for( std::chrono::duration< Rep, Period > const& timeout_duration);
};

}}

4.2. Condition Variables

  • 해당 클래스는 파이버가 다른 파이버의 notification을 기다리는 메커니즘을 제공
  • 파이버가 대기에서 깨어나면 condition이 true인지 확인하고 true이면 계속함
  • condition이 false면 파이버 call은 resume을 위해 waiting
boost::fibers::condition_variable cond;
boost::fibers::mutex mtx;
bool data_ready = false;

void process_data();

void wait_for_data_to_process() {
    {
        std::unique_lock< boost::fibers::mutex > lk( mtx);
        while ( ! data_ready) {
            cond.wait( lk);
        }
    }   // release lk
    process_data();
}

// 다른 방법
void wait_for_data_to_process() {
    {
        std::unique_lock< boost::fibers::mutex > lk( mtx);
        // make condition_variable::wait() perform the loop
        cond.wait( lk, [](){ return data_ready; });
    }   // release lk
    process_data();
}
  • “lk” 는 condition_variable::wait()에 전달됨
  • wait()는 condition variable을 기다리는 파이버 셋에 원자적으로 파이버를 추가하고 mutex를 unlock 함
  • 파이버가 깨어나면, wait() 호출이 return 되기 전에 mutex를 lock
  • 위처럼 하면 다른 파이버가 shared data를 업데이트하기 위해 mutex를 획득할 수 있으며 condition과 관련된 데이터가 안전하게 동기화됨

  • 파이버는 data_ready를 true로 설정한 다음 condition_variable cond에서 condition_variable::notify_one() 또는 condition_variable::notify_all() 을 호출하여 각각 하나의 대기중인 파이버 또는 모든 대기중인 파이버를 깨움
void retrieve_data();
void prepare_data();

void prepare_data_for_processing() {
    retrieve_data();
    prepare_data();
    {
        std::unique_lock< boost::fibers::mutex > lk( mtx);
        data_ready = true;
    }
    cond.notify_one();
}
  • Boost.Fiber는 condition_variable_any 와 condition_variable를 제공
  • condition_variable는 std::unique_lock< boost::fibers::mutex > 에서만 wait가 가능
  • condition_variable_any는 user-defined lock type에서도 wait가 가능

  • Class condition_variable_any
#include <boost/fiber/condition_variable.hpp>

namespace boost {
namespace fibers {

class condition_variable_any {
public:
    condition_variable_any();
    ~condition_variable_any();

    condition_variable_any( condition_variable_any const&) = delete;
    condition_variable_any & operator=( condition_variable_any const&) = delete;

    // wait에 대한 호출에서 *this를 대기중인 파이버들에 대해 block 에서 그중 1개의 파이버를 unblock으로 바꿈
    void notify_one() noexcept;
    // 위와 다르게 대기중인 파이버들에 대해 모든 파이버를 unblock으로 바꿈
    void notify_all() noexcept;

    // 현재 파이버를 block, 파이버는 notify 함수를 받으면 unblock이 가능
    // wait 함수는 https://www.boost.org/doc/libs/1_80_0/libs/fiber/doc/html/fiber/synchronization/conditions.html#condition_variable_any_wait_until 참조
    template< typename LockType >
    void wait( LockType &);

    template< typename LockType, typename Pred >
    void wait( LockType &, Pred);

    template< typename LockType, typename Clock, typename Duration >
    cv_status wait_until( LockType &,
                          std::chrono::time_point< Clock, Duration > const&);

    template< typename LockType, typename Clock, typename Duration, typename Pred >
    bool wait_until( LockType &,
                     std::chrono::time_point< Clock, Duration > const&,
                     Pred);

    template< typename LockType, typename Rep, typename Period >
    cv_status wait_for( LockType &,
                        std::chrono::duration< Rep, Period > const&);

    template< typename LockType, typename Rep, typename Period, typename Pred >
    bool wait_for( LockType &,
                   std::chrono::duration< Rep, Period > const&,
                   Pred);
};

}}
  • Class condition_variable
#include <boost/fiber/condition_variable.hpp>

namespace boost {
namespace fibers {

class condition_variable {
public:
    condition_variable();
    ~condition_variable();

    condition_variable( condition_variable const&) = delete;
    condition_variable & operator=( condition_variable const&) = delete;

    void notify_one() noexcept;
    void notify_all() noexcept;

    void wait( std::unique_lock< mutex > &);

    template< typename Pred >
    void wait( std::unique_lock< mutex > &, Pred);

    template< typename Clock, typename Duration >
    cv_status wait_until( std::unique_lock< mutex > &,
                          std::chrono::time_point< Clock, Duration > const&);

    template< typename Clock, typename Duration, typename Pred >
    bool wait_until( std::unique_lock< mutex > &,
                     std::chrono::time_point< Clock, Duration > const&,
                     Pred);

    template< typename Rep, typename Period >
    cv_status wait_for( std::unique_lock< mutex > &,
                        std::chrono::duration< Rep, Period > const&);

    template< typename Rep, typename Period, typename Pred >
    bool wait_for( std::unique_lock< mutex > &,
                   std::chrono::duration< Rep, Period > const&,
                   Pred);
};

}}

4.3. Barriers

4.4. Channels

4.5. Futures

참고

boost.org fiber


This is personal diary for study documents.
Please comment if I'm wrong or missing something else 😄. 

Top

댓글남기기