|  | Home | Libraries | People | FAQ | More | 
      As noted in the Scheduling section, by default
      Boost.Fiber uses its own round_robin scheduler
      for each thread. To control the way Boost.Fiber
      schedules ready fibers on a particular thread, in general you must follow several
      steps. This section discusses those steps, whereas Scheduling
      serves as a reference for the classes involved.
    
The library's fiber manager keeps track of suspended (blocked) fibers. Only when a fiber becomes ready to run is it passed to the scheduler. Of course, if there are fewer than two ready fibers, the scheduler's job is trivial. Only when there are two or more ready fibers does the particular scheduler implementation start to influence the overall sequence of fiber execution.
In this section we illustrate a simple custom scheduler that honors an integer fiber priority. We will implement it such that a fiber with higher priority is preferred over a fiber with lower priority. Any fibers with equal priority values are serviced on a round-robin basis.
The full source code for the examples below is found in priority.cpp.
The first essential point is that we must associate an integer priority with each fiber.[10]
      One might suggest deriving a custom fiber subclass to store such
      properties. There are a couple of reasons for the present mechanism.
    
fibers::async().) Higher-level
          libraries might introduce additional such wrapper functions. A custom scheduler
          must associate its custom properties with every fiber
          in the thread, not only the ones explicitly launched by instantiating a
          custom fiber subclass.
        fiber
          subclass, we would have to hunt down and modify every place that launches
          a fiber on that thread.
        fiber class is actually just a handle to internal context data.
          A subclass of fiber would
          not add data to context.
        The present mechanism allows you to “drop in” a custom scheduler with its attendant custom properties without altering the rest of your application.
      Instead of deriving a custom scheduler fiber properties subclass from fiber,
      you must instead derive it from fiber_properties.
    
class priority_props : public boost::fibers::fiber_properties { public: priority_props( boost::fibers::context * ctx): fiber_properties( ctx),priority_( 0) { } int get_priority() const { return priority_;
} // Call this method to alter priority, because we must notify // priority_scheduler of any change. void set_priority( int p) {
// Of course, it's only worth reshuffling the queue and all if we're // actually changing the priority. if ( p != priority_) { priority_ = p; notify(); } } // The fiber name of course is solely for purposes of this example // program; it has nothing to do with implementing scheduler priority. // This is a public data member -- not requiring set/get access methods -- // because we need not inform the scheduler of any change. std::string name;
private: int priority_; };
| 
          Your subclass constructor must accept a  | |
| Provide read access methods at your own discretion. | |
| 
          It's important to call  | |
| A property that does not affect the scheduler does not need access methods. | 
      Now we can derive a custom scheduler from algorithm_with_properties<>,
      specifying our custom property class priority_props
      as the template parameter.
    
class priority_scheduler : public boost::fibers::algo::algorithm_with_properties< priority_props > { private: typedef boost::fibers::scheduler::ready_queue_trqueue_t; rqueue_t rqueue_; std::mutex mtx_{}; std::condition_variable cnd_{}; bool flag_{ false }; public: priority_scheduler() : rqueue_() { } // For a subclass of algorithm_with_properties<>, it's important to // override the correct awakened() overload.
virtual void awakened( boost::fibers::context * ctx, priority_props & props) noexcept { int ctx_priority = props.get_priority();
// With this scheduler, fibers with higher priority values are // preferred over fibers with lower priority values. But fibers with // equal priority values are processed in round-robin fashion. So when // we're handed a new context*, put it at the end of the fibers // with that same priority. In other words: search for the first fiber // in the queue with LOWER priority, and insert before that one. rqueue_t::iterator i( std::find_if( rqueue_.begin(), rqueue_.end(), [ctx_priority,this]( boost::fibers::context & c) { return properties( &c ).get_priority() < ctx_priority; })); // Now, whether or not we found a fiber with lower priority, // insert this new fiber here. rqueue_.insert( i, * ctx); }
virtual boost::fibers::context * pick_next() noexcept { // if ready queue is empty, just tell caller if ( rqueue_.empty() ) { return nullptr; } boost::fibers::context * ctx( & rqueue_.front() ); rqueue_.pop_front(); return ctx; }
virtual bool has_ready_fibers() const noexcept { return ! rqueue_.empty(); }
virtual void property_change( boost::fibers::context * ctx, priority_props & props) noexcept { // Although our priority_props class defines multiple properties, only // one of them (priority) actually calls notify() when changed. The // point of a property_change() override is to reshuffle the ready // queue according to the updated priority value. // 'ctx' might not be in our queue at all, if caller is changing the // priority of (say) the running fiber. If it's not there, no need to // move it: we'll handle it next time it hits awakened(). if ( ! ctx->ready_is_linked()) {
return; } // Found ctx: unlink it ctx->ready_unlink(); // Here we know that ctx was in our ready queue, but we've unlinked // it. We happen to have a method that will (re-)add a context* to the // right place in the ready queue. awakened( ctx, props); } void suspend_until( std::chrono::steady_clock::time_point const& time_point) noexcept { if ( (std::chrono::steady_clock::time_point::max)() == time_point) { std::unique_lock< std::mutex > lk( mtx_); cnd_.wait( lk, [this](){ return flag_; }); flag_ = false; } else { std::unique_lock< std::mutex > lk( mtx_); cnd_.wait_until( lk, time_point, [this](){ return flag_; }); flag_ = false; } } void notify() noexcept { std::unique_lock< std::mutex > lk( mtx_); flag_ = true; lk.unlock(); cnd_.notify_all(); } };
| See ready_queue_t. | |
| 
          You must override the  | |
| 
           | |
| 
          You must override the  | |
| 
          You must override  | |
| 
          Overriding  | |
| 
          Your  | 
      Our example priority_scheduler
      doesn't override algorithm_with_properties::new_properties():
      we're content with allocating priority_props
      instances on the heap.
    
      You must call use_scheduling_algorithm() at the start
      of each thread on which you want Boost.Fiber
      to use your custom scheduler rather than its own default round_robin.
      Specifically, you must call use_scheduling_algorithm() before performing any other Boost.Fiber
      operations on that thread.
    
int main( int argc, char *argv[]) { // make sure we use our priority_scheduler rather than default round_robin boost::fibers::use_scheduling_algorithm< priority_scheduler >(); ... }
      The running fiber can access its own fiber_properties subclass
      instance by calling this_fiber::properties(). Although
      properties<>()
      is a nullary function, you must pass, as a template parameter, the fiber_properties subclass.
    
boost::this_fiber::properties< priority_props >().name = "main";
      Given a fiber instance still connected with a running fiber (that
      is, not fiber::detach()ed), you may access that fiber's properties
      using fiber::properties(). As with this_fiber::properties<>(), you must pass your fiber_properties subclass as the template
      parameter.
    
template< typename Fn > boost::fibers::fiber launch( Fn && func, std::string const& name, int priority) { boost::fibers::fiber fiber( func); priority_props & props( fiber.properties< priority_props >() ); props.name = name; props.set_priority( priority); return fiber; }
      Launching a new fiber schedules that fiber as ready, but does not
      immediately enter its fiber-function. The current fiber
      retains control until it blocks (or yields, or terminates) for some other reason.
      As shown in the launch()
      function above, it is reasonable to launch a fiber and immediately set relevant
      properties -- such as, for instance, its priority. Your custom scheduler can
      then make use of this information next time the fiber manager calls algorithm_with_properties::pick_next().
    
[10] A previous version of the Fiber library implicitly tracked an int priority for each fiber, even though the default scheduler ignored it. This has been dropped, since the library now supports arbitrary scheduler-specific fiber properties.