Oliver Kowalke 2013 Oliver Kowalke Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) C++ Library to cooperatively schedule and synchronize micro-threads Fiber
<link linkend="fiber.overview">Overview</link> Boost.Fiber provides a framework for micro-/userland-threads (fibers) scheduled cooperatively. The API contains classes and functions to manage and synchronize fibers similiarly to Boost.Thread. Each fiber has its own stack. A fiber can save the current execution state, including all registers and CPU flags, the instruction pointer, and the stack pointer and later restore this state. The idea is to have multiple execution paths running on a single thread using a sort of cooperative scheduling (versus threads, which are preemptively scheduled). The running fiber decides explicitly when it should yield to allow another fiber to run (context switching). Boost.Fiber internally uses coroutines from Boost.Coroutine; the classes in this library manage, schedule and, when needed, synchronize those coroutines. A context switch between threads usually costs thousands of CPU cycles on x86, compared to a fiber switch with a few hundred cycles. A fiber can only run on a single thread at any point in time. In order to use the classes and functions described here, you can either include the specific headers specified by the descriptions of each class or function, or include the master library header: #include <boost/fiber/all.hpp> which includes all the other headers in turn. The namespaces used are: namespace boost::fibers namespace boost::this_fiber Fibers and Threads Control is cooperatively passed between fibers launched on a given thread. At a given moment, on a given thread, at most one fiber is running. Spawning additional fibers on a given thread does not increase your program's utilization of hardware cores. On the other hand, a fiber may safely access any resource exclusively owned by its parent thread without explicitly needing to defend that resource against concurrent access by other fibers on the same thread. You are already guaranteed that no other fiber on that thread is concurrently touching that resource. This can be particularly important when introducing concurrency in legacy code. You can safely spawn fibers running old code, using asynchronous I/O to interleave execution. In effect, fibers provide a natural way to organize concurrent code based on asynchronous I/O. Instead of chaining together completion handlers, code running on a fiber can make what looks like a normal blocking function call. That call can cheaply suspend the calling fiber, allowing other fibers on the same thread to run. When the operation has completed, the suspended fiber resumes, without having to explicitly save or restore its state. Its local stack variables persist across the call. A fiber launched on a particular thread will always run on that thread. A fiber can count on thread-local storage; however that storage will be shared among all fibers running on the same thread. For fiber-local storage, please see fiber_specific_ptr. Blocking Normally, when this documentation states that a particular fiber blocks, it means that it yields control, allowing other fibers on the same thread to run. The synchronization mechanisms provided by Boost.Fiber have this behavior. A fiber may, of course, use normal thread synchronization mechanisms; however a fiber that invokes any of these mechanisms will block its entire thread, preventing any other fiber from running on that thread in the meantime. For instance, when a fiber wants to wait for a value from another fiber in the same thread, using std::future would be unfortunate: std::future::get() would block the whole thread, preventing the other fiber from delivering its value. Use future<> instead. Similarly, a fiber that invokes a normal blocking I/O operation will block its entire thread. Fiber authors are encouraged to consistently use asynchronous I/O. Boost.Asio explicitly supports fibers; other asynchronous I/O operations can straightforwardly be adapted for Boost.Fiber. Synchronization between a fiber running on one thread and a fiber running on a different thread is an advanced topic. This library is not an official Boost library Boost.Fiber depends upon Boost.Chrono, Boost.Coroutine and Boost.Move. Boost version 1.55.0 or greater is required.
<link linkend="fiber.fiber_mgmt">Fiber management</link> Synopsis #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; class fiber_group; class attributes; struct algorithm; class round_robin; void set_scheduling_algorithm( algorithm * al) } namespace this_fiber { fibers::id get_id() noexcept; void yield(); void sleep_until( fibers::clock_type::time_point const& timeout_point); template< typename Rep, typename Period > void sleep_for( fibers::clock_type::duration< Rep, Period > const& timeout_duration); void interruption_point(); bool interruption_requested() noexcept; bool interruption_enabled() noexcept; class disable_interruption; class restore_interruption; }} Tutorial Each fiber represents a micro-thread which will be launched and managed cooperatively by a scheduler. Objects of type fiber are only moveable. boost::fibers::fiber f1; // not-a-fiber void f() { boost::fibers::fiber f2( some_fn); f1 = boost::move( f2); // f2 moved to f1 } Launching A new fiber is launched by passing an object of a callable type that can be invoked with no parameters. If the object must not (or cannot) be copied, then boost::ref can be used to pass in a reference to the function object. In this case, the user must ensure that the referenced object outlives the newly-created fiber. struct callable { void operator()(); }; boost::fibers::fiber copies_are_safe() { callable x; return boost::fibers::fiber( x); } // x is destroyed, but the newly-created fiber has a copy, so this is OK boost::fibers::fiber oops() { callable x; return boost::fibers::fiber( boost::ref( x) ); } // x is destroyed, but the newly-created fiber still has a reference // this leads to undefined behaviour Control is immediately transferred to the spawned fiber at construction. When the constructor returns, the fiber might be complete or might have suspended. Exceptions Exceptions thrown by the function or callable object passed to the fiber constructor are consumed by the framework. If you need to know which exception was thrown, use future<> and packaged_task<>. Detaching A fiber can be detached by explicitly invoking the fiber::detach() member function. After fiber::detach() is called on a fiber object, that object represents not-a-fiber. The fiber object may then safely be destroyed. boost::fibers::fiber( some_fn).detach(); Joining In order to wait for a fiber to finish, the fiber::join() member function of the fiber object can be used. fiber::join() will block until the fiber object has completed. If the fiber has already completed, or the fiber object represents not-a-fiber, then fiber::join() returns immediately. void some_fn() { ... } boost::fibers::fiber f( some_fn); ... f.join(); If the fiber has already completed, then fiber::join() returns immediately and the joined fiber object becomes not-a-fiber. Destruction When a fiber object representing a valid execution context is destroyed, the program terminates if the fiber is fiber::joinable(). If you intend the fiber to outlive the fiber object that launched it, use the fiber::detach() method. { boost::fibers::fiber f( some_fn); } // std::terminate() will be called { boost::fibers::fiber f(some_fn); f.detach(); } // okay, program continues Interruption A valid fiber can be interrupted by invoking its fiber::interrupt() member function. The next time that fiber executes one of the specific interruption-points with interruption enabled, a fiber_interrupted exception will be thrown. If this exception is not caught, the fiber will be terminated, its stack unwound, its stack objects properly destroyed. With disable_interruption a fiber can avoid being interrupted. // interruption enabled at this point { boost::this_fiber::disable_interruption di1; // interruption disabled { boost::this::fiber::disable_interruption di2; // interruption still disabled } // di2 destroyed; interruption state restored // interruption still disabled } // di destroyed; interruption state restored // interruption enabled At any point, the interruption state for the current thread can be queried by calling this_fiber::interruption_enabled(). The following interruption-points are defined and will throw fiber_interrupted if this_fiber::interruption_requested() and this_fiber::interruption_enabled(). fiber::join() barrier::wait() condition_variable::wait() condition_variable::wait_for() condition_variable::wait_until() this_fiber::sleep_for() this_fiber::sleep_until() this_fiber::interruption_point() Fiber IDs Objects of class fiber::id can be used to identify fibers. Each running fiber has a unique fiber::id obtainable from the corresponding fiber by calling the fiber::get_id() member function. Objects of class fiber::id can be copied, and used as keys in associative containers: the full range of comparison operators is provided. They can also be written to an output stream using the stream insertion operator, though the output format is unspecified. Each instance of fiber::id either refers to some fiber, or not-a-fiber. Instances that refer to not-a-fiber compare equal to each other, but not equal to any instances that refer to an actual fiber. The comparison operators on fiber::id yield a total order for every non-equal fiber::id.
<anchor id="class_fiber"/><link linkend="fiber.fiber_mgmt.fiber">Class <code><phrase role="identifier">fiber</phrase></code></link> #include <boost/fiber/fiber.hpp> class fiber { public: typedef uspecified-class id; fiber() noexcept; template< typename Fn > explicit fiber( Fn fn, attributes const& attr = attributes(), stack_allocator const& stack_alloc = stack_allocator(), std::allocator< fiber > const& alloc = std::allocator< fiber >() ); template< typename Fn, typename StackAllocator > explicit fiber( Fn fn, attributes const& attr, StackAllocator const& stack_alloc, std::allocator< fiber > const& alloc = std::allocator< fiber >() ); template< typename Fn, typename StackAllocator, typename Allocator > explicit fiber( Fn fn, attributes const& attr, StackAllocator const& stack_alloc, Allocator const& alloc); ~fiber(); fiber( fiber const& other) = delete; fiber & operator=( fiber const& other) = delete; fiber( fiber && other) noexcept; fiber & operator=( fiber && other) noexcept; operator safe_bool() const noexcept; bool operator!() const noexcept; void swap( fiber & other) noexcept; bool joinable() const noexcept; id get_id() const noexcept; int priority() const noexcept; void priority( int) noexcept; bool thread_affinity() const noexcept; void thread_affinity( bool) noexcept; void detach() noexcept; void join(); void interrupt() noexcept; }; bool operator<( fiber const& l, fiber const& r) noexcept; void swap( fiber & l, fiber & r) noexcept; Default constructor fiber() noexcept; Effects: Constructs a fiber instance that refers to not-a-fiber. Postconditions: this->get_id() == fiber::id() Throws: Nothing Constructor template< typename Fn > explicit fiber( Fn fn, attributes const& attr = attributes(), stack_allocator const& stack_alloc = stack_allocator(), std::allocator< fiber > const& alloc = std::allocator< fiber >() ); template< typename Fn, typename StackAllocator > explicit fiber( Fn fn, attributes const& attr, StackAllocator const& stack_alloc, std::allocator< fiber > const& alloc = std::allocator< fiber >() ); template< typename Fn, typename StackAllocator, typename Allocator > explicit fiber( Fn fn, attributes const& attr, StackAllocator const& stack_alloc, Allocator const& alloc); Preconditions: Fn must be copyable or movable. Effects: fn is copied into internal storage for access by the new fiber. Postconditions: *this refers to the newly created fiber of execution. Throws: fiber_exception if an error occurs. Note: StackAllocator is required to allocate a stack for the internal coroutine. Move constructor fiber( fiber && other) noexcept; Effects: Transfers ownership of the fiber managed by other to the newly constructed fiber instance. Postconditions: other.get_id() == fiber::id() and get_id() returns the value of other.get_id() prior to the construction Throws: Nothing Move assignment operator fiber & operator=( fiber && other) noexcept; Effects: Transfers ownership of the fiber managed by other (if any) to *this. Postconditions: other->get_id() == fiber::id() and get_id() returns the value of other.get_id() prior to the assignment. Throws: Nothing Destructor ~fiber(); Effects: If the fiber is fiber::joinable(), calls std::terminate. Destroys *this. Throws: Nothing. Note: The programmer must ensure that the destructor is never executed while the fiber is still fiber::joinable(). Even if you know (by calling fiber::operator safe_bool()) that the fiber has completed, you must still call either fiber::join() or fiber::detach() before destroying the fiber object. Member function joinable() bool joinable() const noexcept; Returns: true if *this refers to a fiber of execution, which may or may not have completed; otherwise false. Throws: Nothing See also: fiber::operator safe_bool() Member function join() void join(); Preconditions: the fiber is fiber::joinable(). Effects: If *this refers to a fiber of execution, waits for that fiber to complete. Postconditions: If *this refers to a fiber of execution on entry, that fiber has completed. *this no longer refers to any fiber of execution. Throws: fiber_interrupted if the current fiber is interrupted or system_error Error Conditions: resource_deadlock_would_occur: if this->get_id() == boost::this_fiber::get_id(). invalid_argument: if the fiber is not fiber::joinable(). Notes: join() is one of the predefined interruption-points. Member function detach() void detach(); Preconditions: the fiber is fiber::joinable(). Effects: The fiber of execution becomes detached, and no longer has an associated fiber object. Postconditions: *this no longer refers to any fiber of execution. Throws: system_error Error Conditions: invalid_argument: if the fiber is not fiber::joinable(). Member function get_id() fiber::id get_id() const noexcept; Returns: If *this refers to a fiber of execution, an instance of fiber::id that represents that fiber. Otherwise returns a default-constructed fiber::id. Throws: Nothing See also: this_fiber::get_id() Member function interrupt() void interrupt(); Effects: If *this refers to a fiber of execution, request that the fiber will be interrupted the next time it enters one of the predefined interruption-points with interruption enabled, or if it is currently blocked in a call to one of the predefined interruption-points with interruption enabled. Throws: Nothing Member function priority( int) void priority( int) noexcept; Preconditions: *this refers to a fiber of execution. Effects: set priority for the fiber referenced by *this. Throws: Nothing Note: The meaning of particular int values is determined by the specific algorithm passed to set_scheduling_algorithm(). round_robin and round_robin_ws ignore fiber::priority(). Member function priority() int priority() const noexcept; Preconditions: *this refers to a fiber of execution. Returns: priority for the fiber referenced by *this. Throws: Nothing Member function thread_affinity( bool) void thread_affinity( bool) noexcept; Preconditions: *this refers to a fiber of execution. Effects: Set thread affinity for the fiber referenced by *this. Throws: Nothing Note: "Thread affinity" is only meaningful for certain scheduler algorithms, such as round_robin_ws. Many schedulers ignore this fiber attribute. With a scheduler that might potentially migrate a fiber from its initial thread to another, the value true prevents migration: the fiber will always run on its current thread. The default is false: normally, with an applicable scheduler, a fiber is allowed to migrate across threads. See also: this_fiber::thread_affinity() Member function thread_affinity() bool thread_affinity() const noexcept; Preconditions: *this refers to a fiber of execution. Returns: thread affinity for the fiber referenced by *this. Throws: Nothing See also: this_fiber::thread_affinity() Member function operator safe_bool() operator safe_bool() const noexcept; Returns: true if *this refers to a fiber of execution which has not yet terminated, false otherwise. Compare to fiber::joinable(), which does not distinguish whether the referenced fiber of execution is still running. Throws: Nothing See also: fiber::joinable() Member function operator!() bool operator!() const noexcept; Returns: true if *this does not refer to a fiber of execution or if its fiber of execution has terminated, false otherwise. Throws: Nothing See also: fiber::operator safe_bool(), fiber::joinable() Member function swap() void swap( fiber & other) noexcept; Effects: Exchanges the fiber of execution associated with *this and other, so *this becomes associated with the fiber formerly associated with other, and vice-versa. Postconditions: this->get_id() returns the same value as other.get_id() prior to the call. other.get_id() returns the same value as this->get_id() prior to the call. Throws: Nothing Non-member function swap() void swap( fiber & l, fiber & r) noexcept; Effects: Same as l.swap( r). Non-member function set_scheduling_algorithm() void set_scheduling_algorithm( algorithm* scheduler ); Effects: Directs Boost.Fiber to use scheduler, which must be a concrete subclass of algorithm, as the scheduling algorithm for all fibers in the current thread. Returns: Previous algorithm instance set for the current thread, 0 if this is the first Boost.Fiber entry point called by the current thread. Note: If you want a given thread to use a non-default scheduling algorithm, make that thread call set_scheduling_algorithm() before any other Boost.Fiber entry point. If no scheduler has been set for the current thread by the time Boost.Fiber needs to use it, the library will heap-allocate a default round_robin instance for this thread. Note: set_scheduling_algorithm() does not take ownership of the passed algorithm*: Boost.Fiber does not claim responsibility for the lifespan of the referenced scheduler object. The caller must eventually destroy the passed scheduler, just as it must allocate it in the first place. (Storing the pointer in a boost::thread_specific_ptr is one way to ensure that the instance is destroyed on thread termination.) Throws: Nothing
<anchor id="class_fiber_group"/><link linkend="fiber.fiber_mgmt.fiber_group">Class <code><phrase role="identifier">fiber_group</phrase></code></link> fiber_group represents a collection of fibers which can be collectively waited on or interrupted. fiber_group is neither copyable nor movable. #include <boost/fiber/fiber_group.hpp> class fiber_group { public: fiber_group() {} ~fiber_group(); fiber_group( fiber_group const& other) = delete; fiber_group & operator=( fiber_group const& other) = delete; bool is_this_fiber_in(); bool is_fiber_in( fiber * f); template< typename Fn > fiber * create_fiber( Fn fn, attributes attrs = attributes() ); template< typename Fn, typename StackAllocator > fiber * create_fiber( Fn fn, attributes attrs, StackAllocator const& stack_alloc); template< typename Fn, typename StackAllocator, typename Allocator > fiber * create_fiber( Fn fn, attributes attrs, StackAllocator const& stack_alloc, Allocator const& alloc); void add_fiber( fiber * f); void remove_fiber( fiber * f); void join_all(); void interrupt_all(); std::size_t size() const; }; Constructor fiber_group(); Effects: Create a new fiber group with no fibers. Destructor ~fiber_group(); Effects: Destroy *this and all fiber objects in the group. Member function create_fiber() template< typename Fn > fiber * create_fiber( Fn fn, attributes attrs = attributes() ); template< typename Fn, typename StackAllocator > fiber * create_fiber( Fn fn, attributes attrs, StackAllocator const& stack_alloc); template< typename Fn, typename StackAllocator, typename Allocator > fiber * create_fiber( Fn fn, attributes attrs, StackAllocator const& stack_alloc, Allocator const& alloc); Effects: Create a new fiber object as-if by new fiber( fn) and add it to the group. Postcondition: this->size() is increased by one, the new fiber is running. Returns: A pointer to the new fiber object. Member function add_fiber() void add_fiber( fiber * f); Precondition: The expression delete f is well-formed and will not result in undefined behaviour and is_fiber_in(f) == false. Effects: Add fiber object pointed to by f to the group. Postcondition: this->size() is increased by one. Note: Unless the same f is later passed to remove_fiber(), *this becomes responsible for the lifespan of *f. When the fiber_group is destroyed, *f will also be destroyed. The Precondition implies that f must point to a heap fiber object. This is erroneous: boost::fibers::fiber_group fg; { boost::fibers::fiber f(somefunc); fg.add_fiber(&f); } // WRONG! Instead, use the following: boost::fibers::fiber_group fg; { boost::fibers::fiber* f = new boost::fibers::fiber(somefunc); fg.add_fiber(f); } // okay, fg is now responsible for *f Member function remove_fiber() void remove_fiber( fiber * f); Effects: If f is a member of the group, remove it without calling delete. Postcondition: If f was a member of the group, this->size() is decreased by one. Member function join_all() void join_all(); Requires: is_this_fiber_in() == false. Effects: Call join() on each fiber object in the group. Postcondition: Every fiber in the group has terminated. Note: Since fiber::join() is one of the predefined interruption-points, join_all() is also an interruption point. Member function is_this_fiber_in() bool is_this_fiber_in(); Returns: true if there is a fiber f in the group such that f.get_id() == this_fiber::get_id(). Member function is_fiber_in() bool is_fiber_in( fiber * f); Returns: true if there is a fiber fx in the group such that fx.get_id() == f->get_id(). Member function interrupt_all() void interrupt_all(); Effects: Call interrupt() on each fiber object in the group. Member function size() int size(); Returns: The number of fibers in the group. Throws: Nothing.
<anchor id="class_attributes"/><link linkend="fiber.fiber_mgmt.attributes">Class attributes</link> Class attributes is used to transfer parameters required to set up a fiber's context. #include <boost/fiber/attributes.hpp> struct attributes { std::size_t size; attributes() BOOST_NOEXCEPT; explicit attributes( std::size_t size_) BOOST_NOEXCEPT; }; Constructor attributes(); attributes( std::size_t size); Effects: Parameter size determines the stack size. Throws: Nothing.
<link linkend="fiber.fiber_mgmt.this_fiber">Namespace this_fiber</link> namespace boost { namespace this_fiber { fibers::fiber::id get_id() noexcept; void yield(); void sleep_until( fibers::clock_type::time_point const& sleep_time); template< typename Rep, typename Period > void sleep_for( chrono::duration< Rep, Period > const& timeout_duration); bool thread_affinity() noexcept; void thread_affinity( bool req) noexcept; void interruption_point(); bool interruption_requested() noexcept; bool interruption_enabled() noexcept; class disable_interruption; class restore_interruption; }} Non-member function this_fiber::get_id() #include <boost/fiber/operations.hpp> fiber::id get_id() noexcept; Returns: An instance of fiber::id that represents the currently executing fiber. Throws: Nothing. Non-member function this_fiber::sleep_until() #include <boost/fiber/operations.hpp> void sleep_until( fibers::clock_type & abs_time); Effects: Suspends the current fiber until the time point specified by abs_time has been reached. Throws: fiber_interrupted if the current fiber is interrupted. Note: sleep_until() is one of the predefined interruption-points. Note: The current fiber will not resume before abs_time, but there are no guarantees about how soon after abs_time it might resume. Non-member function this_fiber::sleep_for() #include <boost/fiber/operations.hpp> template< class Rep, class Period > void sleep_for( chrono::duration< Rep, Period > const& rel_time); Effects: Suspends the current fiber until the time duration specified by rel_time has elapsed. Throws: Nothing if operations of chrono::duration<Rep, Period> do not throw exceptions. fiber_interrupted if the current fiber is interrupted. Note: sleep_for() is one of the predefined interruption-points. Note: The current fiber will not resume before rel_time has elapsed, but there are no guarantees about how soon after that it might resume. Non-member function this_fiber::yield() #include <boost/fiber/operations.hpp> void yield(); Effects: Reliquishes execution control, allowing other fibers to run. Throws: fiber_resource_error if an error occurs. Non-member function this_fiber::thread_affinity() #include <boost/fiber/operations.hpp> bool thread_affinity() noexcept; void thread_affinity( bool req) noexcept; Effects: Set or report fiber::thread_affinity() for the currently running fiber. Throws: Nothing. Note: fiber::thread_affinity() is false by default. Non-member function this_fiber::interruption_point() #include <boost/fiber/interruption.hpp> void interruption_point(); Effects: Check to see if the current fiber has been interrupted. Throws: fiber_interrupted if this_fiber::interruption_enabled() and this_fiber::interruption_requested() both return true. Non-member function this_fiber::interruption_requested() #include <boost/fiber/interruption.hpp> bool interruption_requested() noexcept; Returns: true if interruption has been requested for the current fiber, false otherwise. Throws: Nothing. Non-member function this_fiber::interruption_enabled() #include <boost/fiber/interruption.hpp> bool interruption_enabled() noexcept; Returns: true if interruption is enabled for the current fiber, false otherwise. Throws: Nothing. Note: Interruption is enabled by default. Class disable_interruption #include <boost/fiber/interruption.hpp> class disable_interruption { public: disable_interruption() noexcept; ~disable_interruption() noexcept; disable_interruption(const disable_interruption&) = delete; disable_interruption& operator=(const disable_interruption&) = delete; }; Constructor disable_interruption() noexcept; Effects: Stores the current state of this_fiber::interruption_enabled() and disables interruption for the current fiber. Postconditions: this_fiber::interruption_enabled() returns false for the current fiber. Throws: Nothing. Destructor ~disable_interruption() noexcept; Preconditions: Must be called from the same fiber on which *this was constructed. Effects: Restores the state of this_fiber::interruption_enabled() for the current fiber to the state saved at construction of *this. Postconditions: this_fiber::interruption_enabled() for the current fiber returns the value stored by the constructor of *this. Throws: Nothing. Class restore_interruption #include <boost/fiber/interruption.hpp> class restore_interruption { public: explicit restore_interruption(disable_interruption& disabler) noexcept; ~restore_interruption() noexcept; restore_interruption(const restore_interruption&) = delete; restore_interruption& operator=(const restore_interruption&) = delete; }; Constructor explicit restore_interruption(disable_interruption& disabler) noexcept; Preconditions: Must be called from the same fiber on which disabler was constructed. Effects: Restores the current state of this_fiber::interruption_enabled() for the current fiber to that saved in disabler. Postconditions: this_fiber::interruption_enabled() for the current fiber returns the value stored in the constructor of disabler. Throws: Nothing. Destructor ~restore_interruption() noexcept; Preconditions: Must be called from the same fiber on which *this was constructed. Effects: Disables interruption for the current fiber. Postconditions: this_fiber::interruption_enabled() for the current fiber returns false. Throws: Nothing. void foo() { // interruption is enabled { boost::this_fiber::disable_interruption di; // interruption is disabled { boost::this_fiber::restore_interruption ri( di); // interruption now enabled } // ri destroyed, interruption disable again } // di destructed, interruption state restored // interruption now enabled }
<link linkend="fiber.scheduling">Scheduling</link> Fibers are managed by a scheduler which coordinates the sequence of fibers running or waiting. Each thread is required to have its own scheduler. By default, Boost.Fiber allocates for each thread a scheduler ( round_robin) on the heap. To prevent the library from heap-allocating a default scheduler for a given thread, that thread must call set_scheduling_algorithm() before any other Boost.Fiber entry point. void thread_fn() { my_fiber_scheduler mfs; boost::fibers::set_scheduling_algorithm( & mfs); ... } A fiber-scheduler must implement interface algorithm. Boost.Fiber provides two schedulers: round_robin and round_robin_ws. You are explicitly permitted to code your own algorithm subclass, and to pass it to set_scheduling_algorithm(). Class algorithm #include <boost/fiber/algorithm.hpp> struct algorithm { virtual ~algorithm() {} algorithm( algorithm const&) = delete; algorithm & operator=( algorithm const&) = delete; virtual void spawn( detail::fiber_base::ptr_t const&) = 0; virtual void priority( detail::fiber_base::ptr_t const&, int) noexcept = 0; virtual void join( detail::fiber_base::ptr_t const&) = 0; virtual detail::fiber_base::ptr_t active() noexcept = 0; virtual bool run() = 0; virtual void wait( unique_lock< detail::spinlock > &) = 0; virtual bool wait_until( clock_type::time_point const&, unique_lock< detail::spinlock > &) = 0; template< typename Rep, typename Period > bool wait_for( chrono::duration< Rep, Period > const&, unique_lock< detail::spinlock > &); virtual void yield() = 0; virtual detail::fiber_base::id get_main_id() = 0; virtual detail::notify::ptr_t get_main_notifier() = 0; }; void set_scheduling_algorithm( algorithm *); Member function spawn() virtual void spawn( detail::fiber_base::ptr_t const& f) = 0; Effects: Spawns fiber f, e.g. f will be entered the first time or resumed where it was suspended before. Suspends the current fiber. Member function priority() virtual void priority( detail::fiber_base::ptr_t const& f, int p) noexcept = 0; Effects: Sets priority p for fiber f. Interpretation of "priority" is entirely up to the subclass. Throws: Nothing Member function join() virtual void join( detail::fiber_base::ptr_t const& f) = 0; Effects: Suspends the current fiber until fiber f terminates. Member function active() virtual detail::fiber_base::ptr_t active() noexcept = 0; Returns: The active fiber, or a null-pointer if the current execution context is not a fiber managed by this algorithm instance. Note: While the initial context for a given thread is conceptually also a fiber, it has no associated detail::fiber_base instance. This is when active() returns null. Higher-level Boost.Fiber operations treat a thread's initial context as equivalent to an explicitly-launched fiber, but this method operates below that level of abstraction. See also: algorithm::get_main_id() Member function run() virtual bool run() = 0; Effects: Selects one fiber in ready state and runs it until it suspends. The choice of fiber is up to the subclass instance. Returns: Returns true if one fiber was ready and successfully executed, false otherwise. Member function wait() virtual void wait( unique_lock< detail::spinlock > & lk) = 0; Precondition: lk is locked by the current fiber. It locks the spinlock protecting the internal state of a mutex or condition_variable. Effects: Current fiber is set to waiting-state and gets suspended. Postcondition: lk is unlocked. Member function wait_until() virtual bool wait_until( clock_type::time_point const& timeout_time, unique_lock< detail::spinlock > & lk) = 0; Precondition: lk is locked by the current fiber. It locks the spinlock protecting the internal state of a mutex or condition_variable. Effects: Current fiber is set to waiting-state and gets suspended. Returns: Returns true if fiber was resumed before time-point timeout_time was reached, false otherwise. Postcondition: lk is unlocked. Member function wait_for() template< typename Rep, typename Period > bool wait_for( chrono::duration< Rep, Period > const& timeout_duration, unique_lock< detail::spinlock > & lk) Precondition: lk is locked by the current fiber. It locks the spinlock protecting the internal state of a mutex or condition_variable. Effects: Current fiber is set to waiting-state and gets suspended. Returns: Returns true if fiber was resumed before time-duration timeout_duration has passed, false otherwise. Postcondition: lk is unlocked. Member function yield() virtual void yield() = 0; Effects: Suspends active fiber without waiting; that is, the current fiber is immediately set to ready-state. Member function get_main_id() virtual detail::fiber_base::id get_main_id() = 0; Returns: A fiber_base::id associated with the initial context of the thread on which the scheduler is running. Note: While the initial context for a given thread is conceptually also a fiber, it has no associated detail::fiber_base instance. Higher-level Boost.Fiber operations treat a thread's initial context as equivalent to an explicitly-launched fiber, but this method operates below that level of abstraction. See also: algorithm::active() Member function get_main_notifier() virtual detail::notify::ptr_t get_main_notifier() = 0; Returns: A notify::ptr_t associated with the initial context of the thread on which the scheduler is running. Note: While the initial context for a given thread is conceptually also a fiber, it has no associated detail::fiber_base instance. Higher-level Boost.Fiber operations treat a thread's initial context as equivalent to an explicitly-launched fiber, but this method operates below that level of abstraction. The notify::ptr_t is used in condition_variable and mutex to signal threads initial context. See also: algorithm::active() Class round_robin This class implements algorithm and schedules fibers in round-robin fashion. Class round_robin_ws round_robin_ws is intended to be used for migrating fibers between threads (different schedulers). For this purpose the class has two additional functions - steal_from() and migrate_to(). This functionality can be used to implement work-stealing/-sharing in a threadpool. boost::fibers::round_robin_ws rr; boost::fibers::set_scheduling_algorithm( & rr); // steal a fiber from a scheduler `other_rr` running in another thread boost::fibers::fiber f( other_rr->steal_from() ); // check if stealing was successful if ( f) { // migrate stolen fiber to scheduler running in this thread rr.migrate_to( f); // detach fiber f.detach(); }
<link linkend="fiber.stack">Stack allocation</link> A fiber uses internally a coroutine which manages a set of registers and a stack. The memory used by the stack is allocated/deallocated via a stack_allocator which is required to model a stack-allocator concept. stack-allocator concept A stack_allocator must satisfy the stack-allocator concept requirements shown in the following table, in which a is an object of a stack_allocator type, sctx is a stack_context, and size is a std::size_t: expression return type notes a.allocate( sctx, size) void creates a stack of at least size bytes and stores both values in sctx a.deallocate( sctx) void deallocates the stack created by a.allocate() The implementation of allocate() might include logic to protect against exceeding the context's available stack size rather than leaving it as undefined behaviour. Calling deallocate() with a stack_context not previously passed to allocate() results in undefined behaviour. The memory for the stack is not required to be aligned; alignment takes place inside coroutine. Class stack_allocator Boost.Coroutine provides the class coroutine-allocator which models the stack-allocator concept. It appends a guard page at the end of each stack to protect against exceeding the stack. If the guard page is accessed (read or write operation) a segmentation fault/access violation is generated by the operating system. The appended guard page is not mapped to physical memory, only virtual addresses are used. #include <boost/fiber/stack_allocator.hpp> class stack_allocator { static bool is_stack_unbound(); static std::size_t maximum_stacksize(); static std::size_t default_stacksize(); static std::size_t minimum_stacksize(); void allocate( stack_context &, std::size_t size); void deallocate( stack_context &); } Static member function is_stack_unbound() static bool is_stack_unbound(); Returns: Returns true if the environment defines no limit for the size of a stack. Static member function maximum_stacksize() static std::size_t maximum_stacksize(); Preconditions: is_stack_unbound() returns false. Returns: Returns the maximum size in bytes of stack defined by the environment. Static member function default_stacksize() static std::size_t default_stacksize(); Returns: Returns a default stack size, which may be platform specific. If is_stack_unbound() returns true then the present implementation returns the maximum of 64 kB and minimum_stacksize(). Static member function minimum_stacksize() static std::size_t minimum_stacksize(); Returns: Returns the minimum size in bytes of stack required by the environment: Win32 4kB, Win64 8kB, defined by rlimit on POSIX. Member function allocate() void allocate( stack_context &, std::size_t size); Preconditions: minimum_stacksize() > size and ! is_stack_unbound() && ( maximum_stacksize() < size). Effects: Allocates memory of at least size Bytes and stores a pointer to the stack and its actual size in sctx. Returns: void Note: Stores pointer to the start address of the new stack. Depending on the architecture (stack grows downwards vs. upwards), the stored address is the highest/lowest address of the stack. Member function deallocate() void deallocate( stack_context & sctx); Preconditions: sctx.sp is valid, minimum_stacksize() > sctx.size and ! is_stack_unbound() && ( maximum_stacksize() < size). Effects: Deallocates the stack space. Note: sctx must have been set by a previous call to allocate(). Class stack_context Boost.Coroutine provides the class stack_context which will contain the stack pointer and the size of the stack. In case of a segmented-stack, stack_context contains some extra control structures. struct stack_context { void * sp; std::size_t size; // might contain additional control structures, // for instance for segmented stacks } Member variable void * sp Value: Pointer to the beginning of the stack. Note: Whether the "beginning" of the stack is its lowest address or its highest address is architecture-dependent. Member variable std::size_t size Value: Actual size of the stack, in bytes. Segmented stacks Boost.Fiber supports use of a segmented-stack, whose size grows on demand. The fiber is created with a minimal stack size, which will be increased as required. Segmented stacks are currently only supported by gcc from version 4.7 onwards. In order to use a segmented-stack, Boost.Fiber must be built with toolset=gcc segmented-stacks=on at b2/bjam command-line. Applications must be compiled with compiler-flags -fsplit-stack -DBOOST_USE_SEGMENTED_STACKS.
<link linkend="fiber.synchronization">Synchronization</link>
<link linkend="fiber.synchronization.mutex_types">Mutex Types</link> Class mutex #include <boost/fiber/mutex.hpp> class mutex { public: mutex(); ~mutex(); mutex( mutex const& other) = delete; mutex & operator=( mutex const& other) = delete; void lock(); bool try_lock(); void unlock(); typedef unique_lock< mutex > scoped_lock; }; mutex provides an exclusive-ownership mutex. At most one fiber can own the lock on a given instance of mutex at any time. Multiple concurrent calls to lock(), try_lock() and unlock() shall be permitted. Any fiber blocked in lock() is suspended in the scheduler until the owning fiber releases the lock by calling unlock(). Class timed_mutex #include <boost/fiber/timed_mutex.hpp> class timed_mutex : private boost::noncopyable { public: timed_mutex(); ~timed_mutex(); timed_mutex( timed_mutex const& other) = delete; timed_mutex & operator=( timed_mutex const& other) = delete; void lock(); void unlock(); bool try_lock(); bool try_lock_until( clock_type::time_point const& timeout_time); template< typename Rep, typename Period > bool try_lock_for( chrono::duration< Rep, Period > const& timeout_duration) typedef unique_lock< timed_mutex > scoped_lock; }; timed_mutex provides an exclusive-ownership mutex. At most one fiber can own the lock on a given instance of timed_mutex at any time. Multiple concurrent calls to lock(), try_lock(), try_lock_until(), try_lock_for() and unlock() shall be permitted. Class recursive_mutex #include <boost/fiber/recursive_mutex.hpp> class recursive_mutex : private boost::noncopyable { public: recursive_mutex(); ~recursive_mutex(); recursive_mutex( recursive_mutex const& other) = delete; recursive_mutex & operator=( recursive_mutex const& other) = delete; void lock(); bool try_lock() noexcept; void unlock(); typedef unique_lock<recursive_mutex> scoped_lock; }; recursive_mutex provides an exclusive-ownership recursive mutex. At most one fiber can own the lock on a given instance of recursive_mutex at any time. Multiple concurrent calls to lock(), try_lock() and unlock() shall be permitted. A fiber that already has exclusive ownership of a given recursive_mutex instance can call lock() or try_lock() to acquire an additional level of ownership of the mutex. unlock() must be called once for each level of ownership acquired by a single fiber before ownership can be acquired by another fiber. Class recursive_timed_mutex #include <boost/fiber/recursive_timed_mutex.hpp> class recursive_timed_mutex : private boost::noncopyable { 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; void lock(); bool try_lock() noexcept; void unlock(); bool try_lock_until( clock_type::time_point const& timeout_time); template< typename Rep, typename Period > bool try_lock_for( chrono::duration< Rep, Period > const& timeout_duration) typedef unique_lock<recursive_timed_mutex> scoped_lock; }; recursive_timed_mutex provides an exclusive-ownership recursive mutex. At most one fiber can own the lock on a given instance of recursive_timed_mutex at any time. Multiple concurrent calls to lock(), try_lock(), try_lock_for(), try_lock_until() and unlock() shall be permitted. A fiber that already has exclusive ownership of a given recursive_timed_mutex instance can call lock(), try_lock(), try_lock_for() or try_lock_until() to acquire an additional level of ownership of the mutex. unlock() must be called once for each level of ownership acquired by a single fiber before ownership can be acquired by another fiber.
<link linkend="fiber.synchronization.conditions">Condition Variables</link> Synopsis enum class cv_status; { no_timeout, timeout }; class condition_variable; class condition_variable_any; The class condition_variable provides a mechanism for one fiber to wait for notification on condition_variable. When the fiber awakens from the wait, then it checks to see if the appropriate condition is now true, and continues if so. If the condition is not true, then the fiber calls wait again to resume waiting. In the simplest case, this condition is just a boolean variable: boost::fibers::condition_variable cond; boost::fibers::mutex mtx; bool data_ready; void process_data(); void wait_for_data_to_process() { boost::fibers::mutex::scoped_lock lk( mtx); while ( ! data_ready) { cond.wait( lk); } process_data(); } Notice that the lk is passed to wait: wait will atomically add the fiber to the set of fibers waiting on the condition variable, and unlock the mutex. When the fiber is awakened, the mutex will be locked again before the call to wait returns. This allows other fibers to acquire the mutex in order to update the shared data, and ensures that the data associated with the condition is correctly synchronized. In the meantime, another fiber sets the condition to true, and then calls either notify_one or notify_all on the condition variable to wake one waiting fiber or all the waiting fibers respectively. void retrieve_data(); void prepare_data(); void prepare_data_for_processing() { retrieve_data(); prepare_data(); { boost::lock_guard< boost::fibers::mutex > lk( mtx); data_ready = true; } cond.notify_one(); } Note that the same mutex is locked before the shared data is updated, but that the mutex does not have to be locked across the call to notify_one. Boost.Fiber provides both condition_variable and condition_variable_any because Boost.Thread provides both. (Boost.Fiber also provides the name condition, which has been deprecated in Boost.Thread.) However, boost::fiber::condition_variable and boost::fiber::condition_variable_any are the same class; like boost::thread::condition_variable_any, its wait() method will accept any form of lock. boost::fiber::condition_variable_any has no need to further optimize as described for boost::thread::condition_variable. Class condition_variable #include <boost/fiber/condition.hpp> enum cv_status { no_timeout = 1, timeout }; class condition_variable { public: condition_variable(); ~condition_variable(); condition_variable( condition_variable const&) = delete; condition_variable & operator=( condition_variable const&) = delete; void notify_one(); void notify_all(); template< typename LockType > void wait( LockType & lk); template< typename LockType, typename Pred > void wait( LockType & lk, Pred predicate); template< typename LockType > cv_status wait_until( LockType & lk, clock_type::time_point const& timeout_time); template< typename LockType, typename Pred > bool wait_until( LockType & lk, clock_type::time_point const& timeout_time, Pred pred); template< typename LockType, typename Rep, typename Period > cv_status wait_for( LockType & lk, chrono::duration< Rep, Period > const& timeout_duration); template< typename LockType, typename Rep, typename Period, typename Pred > bool wait_for( LockType & lk, chrono::duration< Rep, Period > const& timeout_duration, Pred pred); }; typedef condition_variable condition_variable_any; Constructor condition_variable() Effects: Creates the object. Throws: Nothing. Destructor ~condition_variable() Precondition: All fibers waiting on *this have been notified by a call to notify_one or notify_all (though the respective calls to wait, wait_for or wait_until need not have returned). Effects: Destroys the object. Throws: Nothing. Member function notify_one() void notify_one(); Effects: If any fibers are currently blocked waiting on *this in a call to wait, wait_for or wait_until, unblocks one of those fibers. Throws: Nothing. Note: It is arbitrary which waiting fiber is resumed. Member function notify_all() void notify_all(); Effects: If any fibers are currently blocked waiting on *this in a call to wait, wait_for or wait_until, unblocks all of those fibers. Throws: Nothing. Note: This is why a waiting fiber must also check for the desired program state using a mechanism external to the condition_variable, and retry the wait until that state is reached. A fiber waiting on a condition_variable might well wake up a number of times before the desired state is reached. Templated member function wait() template< typename LockType > void wait( LockType & lk); template< typename LockType, typename Pred > void wait( LockType & lk, Pred pred); Precondition: lk is locked by the current fiber, and either no other fiber is currently waiting on *this, or the execution of the mutex() member function on the lk objects supplied in the calls to wait in all the fibers currently waiting on *this would return the same value as lk->mutex() for this call to wait. Effects: Atomically call lk.unlock() and blocks the current fiber. The fiber will unblock when notified by a call to this->notify_one() or this->notify_all(), or spuriously. When the fiber is unblocked (for whatever reason), the lock is reacquired by invoking lk.lock() before the call to wait returns. The lock is also reacquired by invoking lk.lock() if the function exits with an exception. The member function accepting pred is shorthand for: while ( ! pred()) { wait( lk); } Postcondition: lk is locked by the current fiber. Throws: fiber_exception if an error occurs. fiber_interrupted if the wait was interrupted by a call to fiber::interrupt() on the fiber object associated with the current fiber of execution. Note: The Precondition is a bit dense. It merely states that all the fibers calling wait on *this must wait on lk objects governing the same mutex. Three distinct objects are involved in any condition_variable::wait() call: the condition_variable itself, the mutex coordinating access between fibers and a lock object (e.g. mutex::scoped_lock). In some sense it would be nice if the condition_variable's constructor could accept the related mutex object, enforcing agreement across all wait() calls; but the existing APIs prevent that. Instead we must require the wait() call to accept a reference to the local lock object. It is an error to reuse a given condition_variable instance with lock objects that reference different underlying mutex objects. It would be like a road intersection with traffic lights disconnected from one another: sooner or later a collision will result. Templated member function wait_until() template< typename LockType > cv_status wait_until( LockType & lk, clock_type::time_point const& timeout_time); template< typename LockType, typename Pred > bool wait_until( LockType & lk, clock_type::time_point const& timeout_time, Pred pred); Precondition: lk is locked by the current fiber, and either no other fiber is currently waiting on *this, or the execution of the mutex() member function on the lk objects supplied in the calls to wait, wait_for or wait_until in all the fibers currently waiting on *this would return the same value as lk->mutex() for this call to wait. Effects: Atomically call lk.unlock() and blocks the current fiber. The fiber will unblock when notified by a call to this->notify_one() or this->notify_all(), when the time as reported by clock_type::now() would be equal to or later than the specified timeout_time, or spuriously. When the fiber is unblocked (for whatever reason), the lock is reacquired by invoking lk.lock() before the call to wait returns. The lock is also reacquired by invoking lk.lock() if the function exits with an exception. The member function accepting pred is shorthand for: while ( ! pred() ) { if ( cv_status::timeout == wait_until( lk, timeout_time) ) return pred(); } return true; That is, even if wait_until() times out, it can still return true if pred() returns true at that time. Postcondition: lk is locked by the current fiber. Throws: fiber_exception if an error occurs. fiber_interrupted if the wait was interrupted by a call to fiber::interrupt() on the fiber object associated with the current fiber of execution. Returns: The overload without pred returns cv_status::no_timeout if awakened by notify_one() or notify_all(), or cv_status::timeout if awakened because clock_type::now() is past timeout_time. Returns: The overload accepting pred returns false if the call is returning because the time specified by timeout_time was reached and the predicate returns false, true otherwise. Note: See Note for wait(). Templated member function wait_for() template< typename LockType, typename Rep, typename Period > cv_status wait_for( LockType & lk, chrono::duration< Rep, Period > const& timeout_duration); template< typename LockType, typename Rep, typename Period, typename Pred > bool wait_for( LockType & lk, chrono::duration< Rep, Period > const& timeout_duration, Pred pred); Precondition: lk is locked by the current fiber, and either no other fiber is currently waiting on *this, or the execution of the mutex() member function on the lk objects supplied in the calls to wait, wait_for or wait_until in all the fibers currently waiting on *this would return the same value as lk->mutex() for this call to wait. Effects: Atomically call lk.unlock() and blocks the current fiber. The fiber will unblock when notified by a call to this->notify_one() or this->notify_all(), when a time interval equal to or greater than the specified timeout_duration has elapsed, or spuriously. When the fiber is unblocked (for whatever reason), the lock is reacquired by invoking lk.lock() before the call to wait returns. The lock is also reacquired by invoking lk.lock() if the function exits with an exception. The member function accepting pred is shorthand for: while ( ! pred() ) { if ( cv_status::timeout == wait_for( lk, timeout_duration) ) return pred(); } return true; That is, even if wait_for() times out, it can still return true if pred() returns true at that time. Postcondition: lk is locked by the current fiber. Throws: fiber_exception if an error occurs. fiber_interrupted if the wait was interrupted by a call to fiber::interrupt() on the fiber object associated with the current fiber of execution. Returns: The overload without pred returns cv_status::no_timeout if awakened by notify_one() or notify_all(), or cv_status::timeout if awakened because at least timeout_duration has elapsed. Returns: The overload accepting pred returns false if the call is returning because at least timeout_duration has elapsed and the predicate returns false, true otherwise. Note: See Note for wait().
<link linkend="fiber.synchronization.barriers">Barriers</link> A barrier is a concept also known as a rendezvous, it is a synchronization point between multiple contexts of execution (fibers). The barrier is configured for a particular number of fibers (n), and as fibers reach the barrier they must wait until all n fibers have arrived. Once the n-th fiber has reached the barrier, all the waiting fibers can proceed, and the barrier is reset. Class barrier #include <boost/fiber/barrier.hpp> class barrier { public: barrier( unsigned int initial); barrier( barrier const& other) = delete; barrier & operator=( barrier const& other) = delete; bool wait(); }; Instances of barrier are not copyable or movable. Constructor barrier( unsigned int initial); Effects: Construct a barrier for initial fibers. Throws: std::invalid_argument if initial is zero Member function wait() bool wait(); Effects: Block until initial fibers have called wait on *this. When the initial-th fiber calls wait, all waiting fibers are unblocked, and the barrier is reset. Returns: true for exactly one fiber from each batch of waiting fibers, false otherwise. Throws: fiber_exception Note: interruption point
<link linkend="fiber.synchronization.queues">Queues</link> Boost.Fiber provides a bounded and a unbounded queue suitable to synchonize fibers via message passing. typedef boost::fibers::unbounded_queue< int > queue_t; void send( queue_t & queue) { for ( int i = 0; i < 5; ++i) queue.push( i); queue.close(); } void recv( queue_t & queue) { int i; while ( boost::fibers::queue_op_status::success == queue.pop(i) ) { std::cout << "received " << i << std::endl; } } queue_t queue; boost::fibers::fiber f1( boost::bind( send, ref( queue) ) ); boost::fibers::fiber f2( boost::bind( recv, ref( queue) ) ); f1.join(); f2.join(); Enumeration queue_op_status Queue operations return the state of the queue. enum class queue_op_status { success, empty, full, closed, timeout }; success Effects: Operation was successful. empty Effects: Queue is empty, operation failed. full Effects: Queue is full, operation failed. closed Effects: Queue is closed, operation failed. timeout Effects: The operation did not become ready before specified timeout elapsed. Template unbounded_queue<> #include <boost/fiber/unbounded_queue.hpp> template< typename T > class unbounded_queue { public: typedef T value_type; unbounded_queue( unbounded_queue const& other) = delete; unbounded_queue & operator=( unbounded_queue const& other) = delete; bool is_closed() const; void close(); bool is_empty(); void push( value_type const& va); void push( value_type && va); queue_op_status pop( value_type & va); template< typename Rep, typename Period > queue_op_status pop_wait_for( value_type & va, chrono::duration< Rep, Period > const& timeout_duration); queue_op_status pop_wait_until( value_type & va, clock_type::time_point const& timeout_time); queue_op_status try_pop( value_type & va); }; Member function is_closed() bool is_closed() const; Returns: true if queue has been closed. Throws: Nothing. Note: The queue is not closed by default. Member function close() void close(); Effects: Deactivates the queue. No values can be put after calling this->close(). Fibers blocked in this->pop(), this->pop_wait_for() or this->pop_wait_until() will return closed. Throws: Nothing. Note: close() is like closing a pipe. It informs waiting consumers that no more values will arrive. Member function is_empty() bool is_empty(); Effects: Returns true if the queue currently contains no data. Throws: Nothing. Note: This condition is transient. An is_empty() queue can become non-empty. Member function push() void push( value_type const& va); void push( value_type && va); Effects: Enqueues the value in the queue and wakes up a fiber blocked on this->pop(), this->pop_wait_for() or this->pop_wait_until(). Throws: Nothing. Member function pop() queue_op_status pop( value_type & va); Effects: Dequeues a value from the queue. If the queue is_empty(), the fiber gets suspended until at least one new item is push()ed (return value success and va contains dequeued value) or the queue gets close()d (return value closed). Throws: fiber_interrupted Member function pop_wait_for() template< typename Rep, typename Period > queue_op_status pop_wait_for( value_type & va, chrono::duration< Rep, Period > const& timeout_duration) Effects: Accepts chrono::duration and internally computes a clock_type::time_point as (clock_type::now() + timeout_duration). If (! this->is_empty()), immediately dequeues a value from the queue. Otherwise the fiber gets suspended until at least one new item is push()ed (return value success and va contains dequeued value), or the queue gets close()d (return value closed), or the time as reported by clock_type::now() reaches the computed clock_type::time_point (return value timeout). Throws: fiber_interrupted Member function pop_wait_until() queue_op_status pop_wait_until( value_type & va, clock_type::time_point const& timeout_time) Effects: Accepts a clock_type::time_point. If (! this->is_empty()), immediately dequeues a value from the queue. Otherwise the fiber gets suspended until at least one new item is push()ed (return value success and va contains dequeued value), or the queue gets close()d (return value closed), or the time as reported by clock_type::now() reaches the passed clock_type::time_point (return value timeout). Throws: fiber_interrupted Member function try_pop() queue_op_status try_pop( value_type & va); Effects: If this->is_empty(), returns empty. If this->is_closed(), returns closed. Otherwise it returns success and va contains the dequeued value. Throws: Nothing. Template bounded_queue<> #include <boost/fiber/bounded_queue.hpp> template< typename T > class bounded_queue { public: typedef T value_type; bounded_queue( bounded_queue const& other) = delete; bounded_queue & operator=( bounded_queue const& other) = delete; bounded_queue( std::size_t wm); bounded_queue( std::size_t hwm, std::size_t lwm); bool is_closed() const; void close(); bool is_empty(); bool is_full(); queue_op_status push( value_type const& va); queue_op_status push( value_type && va); template< typename Rep, typename Period > queue_op_status push_wait_for( value_type const& va, chrono::duration< Rep, Period > const& timeout_duration); template< typename Rep, typename Period > queue_op_status push_wait_for( value_type && va, chrono::duration< Rep, Period > const& timeout_duration); queue_op_status push_wait_until( value_type const& va, clock_type::time_point const& timeout_time); queue_op_status push_wait_until( value_type && va, clock_type::time_point const& timeout_time); queue_op_status try_push( value_type const& va); queue_op_status try_push( value_type && va); queue_op_status pop( value_type & va); template< typename Rep, typename Period > queue_op_status pop_wait_for( value_type & va, chrono::duration< Rep, Period > const& timeout_duration); queue_op_status pop_wait_until( value_type & va, clock_type::time_point const& timeout_time); queue_op_status try_pop( value_type & va); }; Constructor bounded_queue( std::size_t wm); bounded_queue( std::size_t hwm, std::size_t lwm); Preconditions: hwm >= lwm Effects: Constructs an object of class bounded_queue. The constructor with two arguments constructs an object of class bounded_queue with a high-watermark of hwm and a low-watermark of lwm items. The constructor with one argument effectively sets both hwm and lwm to the same value wm. Throws: invalid_argument if lwm > hwm. Notes: Once the number of values in the queue reaches hwm, any call to push(), push_wait_for() or push_wait_until() will block until the number of values in the queue has dropped below lwm. That is, if lwm < hwm, the queue can be in a state in which push(), push_wait_for() or push_wait_until() calls will block (is_full() returns true) even though the number of values in the queue is less than hwm. Member function is_closed() bool is_closed() const; Returns: true if queue has been closed. Throws: Nothing. Note: The queue is not closed by default. Member function close() void close(); Effects: Deactivates the queue. No values can be put after calling this->close(). Fibers blocked in this->pop(), this->pop_wait_for() or this->pop_wait_until() will return closed. Throws: Nothing. Note: close() is like closing a pipe. It informs waiting consumers that no more values will arrive. Member function is_empty() bool is_empty(); Effects: Returns true if the queue currently contains no data. Throws: Nothing. Note: This condition is transient. An is_empty() queue can become non-empty. Member function is_full() bool is_full(); Effects: Returns true if the queue cannot accept more data at present. This happens when the number of values in the queue reaches wm or hwm. Once the queue becomes full, however, it continues refusing new values until the number of values drops below lwm. Throws: Nothing. Note: This condition is transient. Member function push() queue_op_status push( value_type const& va); queue_op_status push( value_type && va); Effects: If this->is_closed(), returns closed. If (! this->is_full()), enqueues the value in the queue, wakes up a fiber blocked on this->pop(), this->pop_wait_for() or this->pop_wait_until() and returns success. Otherwise the fiber gets suspended until the number of values in the queue drops below lwm (return value success), or the queue is close()d (return value closed). Throws: fiber_interrupted Member function push_wait_for() template< typename Rep, typename Period > queue_op_status push_wait_for( value_type const& va, chrono::duration< Rep, Period > const& timeout_duration); template< typename Rep, typename Period > queue_op_status push_wait_for( value_type && va, chrono::duration< Rep, Period > const& timeout_duration); Effects: Accepts chrono::duration and internally computes a clock_type::time_point as (clock_type::now() + timeout_duration). If this->is_closed(), returns closed. If (! this->is_full()), enqueues the value in the queue, wakes up a fiber blocked on this->pop() this->pop_wait_for() or this->pop_wait_until() and returns success. Otherwise the fiber gets suspended until the number of values in the queue drops below lwm (return value success), the queue is close()d (return value closed), or the time as reported by clock_type::now() reaches the computed clock_type::time_point (return value timeout). Throws: fiber_interrupted Member function push_wait_until() queue_op_status push_wait_until( value_type const& va, clock_type::time_point const& timeout_time); queue_op_status push_wait_until( value_type && va, clock_type::time_point const& timeout_time); Effects: Accepts a clock_type::time_point. If this->is_closed(), returns closed. If (! this->is_full()), enqueues the value in the queue, wakes up a fiber blocked on this->pop(), this->pop_wait_for() or this->pop_wait_until() and returns success. Otherwise the fiber gets suspended until the number of values in the queue drops below lwm (return value success), the queue is close()d (return value closed), or the time as reported by clock_type::now() reaches the passed clock_type::time_point (return value timeout). Throws: fiber_interrupted Member function try_push() queue_op_status try_push( value_type const& va); queue_op_status try_push( value_type && va); Effects: If this->is_full(), returns full. If this->is_closed(), returns closed. Otherwise enqueues the value in the queue, wakes up a fiber blocked on this->pop(), this->pop_wait_for() or this->pop_wait_until() and returns success. Throws: fiber_interrupted Member function pop() queue_op_status pop( value_type & va); Effects: Dequeues a value from the queue. If the queue is_empty(), the fiber gets suspended until at least one new item is push()ed (return value success and va contains dequeued value) or the queue gets close()d (return value closed). Once the number of items remaining in the queue drops below lwm, any fibers blocked on push(), push_wait_for() or push_wait_until() may resume. Throws: fiber_interrupted Member function pop_wait_for() template< typename Rep, typename Period > queue_op_status pop_wait_for( value_type & va, chrono::duration< Rep, Period > const& timeout_duration) Effects: Accepts chrono::duration and internally computes a clock_type::time_point as (clock_type::now() + timeout_duration). If (! this->is_empty()), immediately dequeues a value from the queue. Otherwise the fiber gets suspended until at least one new item is push()ed (return value success and va contains dequeued value), or the queue gets close()d (return value closed), or the time as reported by clock_type::now() reaches the computed clock_type::time_point (return value timeout). Throws: fiber_interrupted Member function pop_wait_until() queue_op_status pop_wait_until( value_type & va, clock_type::time_point const& timeout_time) Effects: Accepts a clock_type::time_point. If (! this->is_empty()), immediately dequeues a value from the queue. Otherwise the fiber gets suspended until at least one new item is push()ed (return value success and va contains dequeued value), or the queue gets close()d (return value closed), or the time as reported by clock_type::now() reaches the passed clock_type::time_point (return value timeout). Throws: fiber_interrupted Member function try_pop() queue_op_status try_pop( value_type & va); Effects: If this->is_empty(), returns empty. If this->is_closed(), returns closed. Otherwise it returns success and va contains the dequeued value. Throws: Nothing.
<link linkend="fiber.synchronization.futures">Futures</link> Overview The futures library provides a means of handling synchronous future values, whether those values are generated by another fiber, or on a single fiber in response to external stimuli, or on-demand. This is done through the provision of four class templates: future<> and shared_future<> which are used to retrieve the asynchronous results, and promise<> and packaged_task<> which are used to generate the asynchronous results. An instance of future<> holds the one and only reference to a result. Ownership can be transferred between instances using the move constructor or move-assignment operator, but at most one instance holds a reference to a given asynchronous result. When the result is ready, it is returned from future::get() by rvalue-reference to allow the result to be moved or copied as appropriate for the type. On the other hand, many instances of shared_future<> may reference the same result. Instances can be freely copied and assigned, and shared_future::get() returns a non const reference so that multiple calls to shared_future::get() are safe. You can move an instance of future<> into an instance of shared_future<>, thus transferring ownership of the associated asynchronous result, but not vice-versa. async() is a simple way of running asynchronous tasks. A call to async() returns a future<> that will deliver the result of the task. Creating asynchronous values You can set the value in a future with either a promise<> or a packaged_task<>. A packaged_task<> is a callable object with void return that wraps a function or callable object returning the specified type. When the packaged_task<> is invoked, it invokes the contained function in turn, and populates a future with the contained function's return value. This is an answer to the perennial question: "How do I return a value from a fiber?" Package the function you wish to run as a packaged_task<> and pass the packaged task to the fiber constructor. The future retrieved from the packaged task can then be used to obtain the return value. If the function throws an exception, that is stored in the future in place of the return value. int calculate_the_answer_to_life_the_universe_and_everything() { return 42; } boost::fibers::packaged_task<int()> pt(calculate_the_answer_to_life_the_universe_and_everything); boost::fibers::future<int> fi=pt.get_future(); boost::fibers::fiber(boost::move(pt)).detach(); // launch task on a fiber fi.wait(); // wait for it to finish assert(fi.is_ready()); assert(fi.has_value()); assert(!fi.has_exception()); assert(fi.get()==42); A promise<> is a bit more low level: it just provides explicit functions to store a value or an exception in the associated future. A promise can therefore be used where the value might come from more than one possible source, or where a single operation may produce multiple values. boost::fibers::promise<int> pi; boost::fibers::future<int> fi; fi=pi.get_future(); pi.set_value(42); assert(fi.is_ready()); assert(fi.has_value()); assert(!fi.has_exception()); assert(fi.get()==42);
<link linkend="fiber.synchronization.futures.future">Future</link> A future provides a mechanism to access the result of an asynchronous operation. Enumeration future_status Timed wait-operations ( future::wait_for() and future::wait_until()) return the state of the future. enum class future_status { ready, timeout, deferred }; ready Effects: The shared state is ready. timeout Effects: The shared state did not become ready before timout has passed. deferred Effects: The function is deferred, e.g. result will be computed only when explictly requested. Template future<> A future<> contains a shared state which is not shared with any other future. template< typename R > class future { public: future() noexcept; future( future && other) noexcept; future( future const& other) = delete; ~future(); future & operator=( future && other) noexcept; future & operator=( future const& other) = delete; void swap( future & other) noexcept; operator safe_bool() const noexcept; bool operator!() const noexcept; bool valid() const noexcept; shared_future< R > share(); R get(); void wait() const; template< class Rep, class Period > future_status wait_for( chrono::duration< Rep, Period > const& timeout_duration) const; future_status wait_until( clock_type::time_point const& timeout_time) const; }; Default constructor future(); Effects: Creates a future with no shared state. After construction is false == vaild(). Throws: Nothing. Move constructor future( future && other) noexcept; Effects: Constructs a future with the shared state of other. After construction is false == other.valid() Throws: Nothing. Destructor ~future(); Effects: Destructs the future; ownership is abandoned. Throws: Nothing. Member function operator=() future & operator=( future && other) noexcept; Effects: Moves the shared state of other to this. After construction is false == other.valid() Throws: Nothing. Member function swap() void swap( future & other) noexcept; Effects: Swaps the shared state between other and this. Throws: Nothing. Member function operator safe_bool() operator safe_bool() const noexcept; Effects: Returns true if future is valid. Throws: Nothing. Member function operator!() bool operator!() const noexcept; Effects: Returns false if future is valid. Throws: Nothing. Member function valid() bool valid() const noexcept; Effects: Returns true if future contains a shared state. Throws: Nothing. Member function share() shared_future< R > share(); Effects: Move the state to a shared_future<>. Throws: future_error with error condtion future_errc::no_state. Member function get() R get(); Precondition: true == valid() Effects: Returns the result. Postcondition: false == valid() Throws: future_error with error condtion future_errc::no_state. Member function wait() void wait(); Effects: Waits for the result will become available. Throws: future_error with error condtion future_errc::no_state. Templated member function wait_for() template< class Rep, class Period > future_status wait_for( chrono::duration< Rep, Period > const& timeout_duration) const; Effects: Waits for the result will become available or timeout_duration has passed. Result: A future_status is returned indicating the reason for returning. Throws: future_error with error condtion future_errc::no_state. Member function wait_until() future_status wait_until( clock_type::time_point const& timeout_time) const; Effects: Waits for the result will become available or timeout_time has passed. Result: A future_status is returned indicating the reason for returning. Throws: future_error with error condtion future_errc::no_state. Template shared_future<> A shared_future<> contains a shared state which might be shared with other futures. template< typename R > class shared_future { public: shared_future() noexcept; ~shared_future(); shared_future( shared_future const& other); shared_future( future< R > && other) noexcept; shared_future( shared_future && other) noexcept; shared_future & operator=( shared_future && other) noexcept; shared_future & operator=( future && other) noexcept; shared_future & operator=( shared_future const& other) noexcept; void swap( shared_future & other) noexcept; operator safe_bool() const noexcept; bool operator!() const noexcept; bool valid() const noexcept; R get(); void wait() const; template< class Rep, class Period > future_status wait_for( chrono::duration< Rep, Period > const& timeout_duration) const; future_status wait_until( clock_type::time_point const& timeout_time) const; }; Default constructor shared_future(); Effects: Creates a future with no shared state. After construction is false == vaild(). Throws: Nothing. Move constructor shared_future( future && other) noexcept; shared_future( shared_future && other) noexcept; Effects: Constructs a future with the shared state of other. After construction is false == other.valid(). Throws: Nothing. Copy constructor shared_future( shared_future cosnt& other) noexcept; Effects: Constructs a future with the shared state of other. After construction is true == other.valid(). Throws: Nothing. Destructor ~shared_future(); Effects: Destructs the future; ownership is abandoned if not shared. Throws: Nothing. Member function operator=() shared_future & operator=( future && other) noexcept; shared_future & operator=( shared_future && other) noexcept; shared_future & operator=( shared_future const& other) noexcept; Effects: Transfers the ownership of shared state according to: shared_future tmp( other); swap( tmp); return * this; Throws: Nothing. Member function swap() void swap( shared_future & other) noexcept; Effects: Swaps the shared state between other and this. Throws: Nothing. Member function operator safe_bool() operator safe_bool() const noexcept; Effects: Returns true if future is valid. Throws: Nothing. Member function operator!() bool operator!() const noexcept; Effects: Returns false if future is valid. Throws: Nothing. Member function valid() bool valid() const noexcept; Effects: Returns true if future contains a shared state. Throws: Nothing. Member function get() R get(); Precondition: true == valid() Effects: Returns the result. Postcondition: false == valid() Throws: future_error with error condtion future_errc::no_state. Member function wait() void wait(); Effects: Waits for the result will become available. Throws: future_error with error condtion future_errc::no_state. Templated member function wait_for() template< class Rep, class Period > future_status wait_for( chrono::duration< Rep, Period > const& timeout_duration) const; Effects: Waits for the result will become available or timeout_duration has passed. Result: A future_status is returned indicating the reason for returning. Throws: future_error with error condtion future_errc::no_state. Member function wait_until() future_status wait_until( clock_type::time_point const& timeout_time) const; Effects: Waits for the result will become available or timeout_time has passed. Result: A future_status is returned indicating the reason for returning. Throws: future_error with error condtion future_errc::no_state.
<anchor id="class_promise"/><link linkend="fiber.synchronization.futures.promise">Template <code><phrase role="identifier">promise</phrase><phrase role="special"><></phrase></code></link> A promise<> provides a mechanism to store a value that can later be accessed asynchronously by a future<> object. template< typename R > class promise { public: promise(); template< typename Allocator > promise( boost::allocator_arg_t, Allocator alloc); promise( promise && other) noexcept; promise( promise const& other) = delete; ~promise(); promise & operator=( promise && other) noexcept; promise & operator=( promise const& other) = delete; void swap( promise & other) noexcept; operator safe_bool() const noexcept; bool operator!() const noexcept; future< R > get_future(); void set_value( R const& value); void set_value( R && value); void set_exception( exception_ptr p); }; Default constructor promise(); Effects: Creates a promise with an empty shared state. Throws: Nothing. Constructor template< typename Allocator > promise( boost::allocator_arg_t, Allocator alloc); Effects: Creates a promise with an empty shared state by using alloc. Throws: Nothing. Move constructor promise( promise && other) noexcept; Effects: Creates a promise by moving the shared state from other. Postcondition: other contains no valid shared state. Throws: Nothing. Destructor ~promise(); Effects: Destroys *this and abandons the shared state if shared state is ready; otherwise stores future_error with error condition future_errc::broken_promise. Throws: Nothing. Member function operator=() promise & operator=( promise && other) noexcept; Effects: Transfers the ownership of shared state to *this. Postcondition: other contains no valid shared state. Throws: Nothing. Member function swap() void swap( promise & other) noexcept; Effects: Swaps the shared state between other and *this. Throws: Nothing. Member function operator safe_bool() operator safe_bool() const noexcept; Effects: Returns true if *this contains a non-empty shared state. Throws: Nothing. Member function operator!() bool operator!() const noexcept; Effects: Returns true if *this contains an empty shared state. Throws: Nothing. Member function get_future() future< R > get_future(); Returns: A future<> with the same shared state. Throws: future_error with future_errc::future_already_retrieved or future_errc::no_state. Member function set_value() void set_value( R const& value); void set_value( R && value); Effects: Store the result in the shared state and marks the state as ready. Throws: future_error with future_errc::future_already_satisfied or future_errc::no_state. Member function set_exception() void set_exception( exception_ptr); Effects: Store an exception pointer in the shared state and marks the state as ready. Throws: future_error with future_errc::future_already_satisfied or future_errc::no_state.
<anchor id="class_packaged_task"/><link linkend="fiber.synchronization.futures.packaged_task">Template <code><phrase role="identifier">packaged_task</phrase><phrase role="special"><></phrase></code></link> A packaged_task<> wraps a callable target that returns a value so that the return value can be computed asynchronously. template< class R > class packaged_task< R() > { public: packaged_task() noexcept; template< typename Fn > explicit packaged_task( Fn && fn); template< typename Fn, typename Allocator > explicit packaged_task( boost::allocator_arg_t, Allocator const& alloc, Fn && fn); packaged_task( packaged_task && other) noexcept; packaged_task( packaged_task const& other) = delete; ~packaged_task(); packaged_task & operator=( packaged_task && other) noexcept; packaged_task & operator=( packaged_task const& other) = delete; void swap( packaged_task & other) noexcept; operator safe_bool() const noexcept; bool operator!() const noexcept; bool valid() const noexcept; future< R > get_future(); void operator()(); void reset(); }; Default constructor packaged_task() packaged_task(); Effects: Constructs an object of class packaged_task with no shared state. Throws: Nothing. Templated constructor template<> packaged_task() template< typename Fn > explicit packaged_task( Fn && fn); template< typename Fn, typename Allocator > explicit packaged_task( boost::allocator_arg_t, Allocator const& alloc, Fn && fn); Effects: Constructs an object of class packaged_task with a shared state and stores the callable target fn internally. Throws: Nothing. Note: The signature of Fn should have a return type convertible to R. Move constructor packaged_task( packaged_task && other) noexcept; Effects: Creates a packaged_task by moving the shared state from other. Postcondition: other contains no valid shared state. Throws: Nothing. Destructor ~packaged_task(); Effects: Destroys *this and abandons the shared state if shared state is ready; otherwise stores future_error with error condition future_errc::broken_promise. Throws: Nothing. Member function operator=() packaged_task & operator=( packaged_task && other) noexcept; Effects: Transfers the ownership of shared state to *this. Postcondition: other contains no valid shared state. Throws: Nothing. Member function swap() void swap( packaged_task & other) noexcept; Effects: Swaps the shared state between other and *this. Throws: Nothing. Member function operator safe_bool() operator safe_bool() const noexcept; Effects: Returns true if *this contains a non-empty shared state. Throws: Nothing. Member function operator!() bool operator!() const noexcept; Effects: Returns true if *this contains an empty shared state. Throws: Nothing. Member function valid() bool valid() const noexcept; Effects: Returns true if *this contains a shared state. Throws: Nothing. Member function get_future() future< R > get_future(); Returns: A future<> with the same shared state. Throws: future_error with future_errc::future_already_retrieved or future_errc::no_state. Member function operator()() void operator()(); Effects: Invokes the stored callable target. Any exception thrown by the callable target fn is stored in the shared state. Otherwise, the value returned by fn is stored in the shared state. Throws: future_error with future_errc::no_state. Member function reset() void reset(); Effects: Resets the shared state and abondons the result of previous executions. A new shared state is constructed. Throws: future_error with future_errc::no_state.
<link linkend="fiber.fls">Fiber local storage</link> Synopsis Fiber local storage allows a separate instance of a given data item for each fiber. Cleanup at fiber exit When a fiber exits, the objects associated with each fiber_specific_ptr instance are destroyed. By default, the object pointed to by a pointer p is destroyed by invoking delete p, but this can be overridden for a specific instance of fiber_specific_ptr by providing a cleanup routine func to the constructor. In this case, the object is destroyed by invoking func(p). The cleanup functions are called in an unspecified order. Class fiber_specific_ptr #include <boost/fiber/fss.hpp> template< typename T > class fiber_specific_ptr { public: typedef T element_type; fiber_specific_ptr(); explicit fiber_specific_ptr( void(*fn)(T*) ); ~fiber_specific_ptr(); fiber_specific_ptr( fiber_specific_ptr const& other) = delete; fiber_specific_ptr & operator=( fiber_specific_ptr const& other) = delete; T * get() const; T * operator->() const; T & operator*() const; T * release(); void reset( T * t); }; Constructor fiber_specific_ptr(); explicit fiber_specific_ptr( void(*fn)(T*) ); Requires: delete this->get() is well-formed; fn(this->get()) does not throw Effects: Construct a fiber_specific_ptr object for storing a pointer to an object of type T specific to each fiber. When reset() is called, or the fiber exits, fiber_specific_ptr calls fn(this->get()). If the no-arguments constructor is used, the default delete-based cleanup function will be used to destroy the fiber-local objects. Throws: fiber_resource_error if an error occurs. Destructor ~fiber_specific_ptr(); Requires: All the fiber specific instances associated to this fiber_specific_ptr (except maybe the one associated to this fiber) must be null. Effects: Calls this->reset() to clean up the associated value for the current fiber, and destroys *this. Throws: Nothing. Remarks: The requirement is due to the fact that in order to delete all these instances, the implementation should be forced to maintain a list of all the fibers having an associated specific ptr, which is against the goal of fiber specific data. In general, a fiber_specific_ptr should outlive the fibers that use it. Care needs to be taken to ensure that any fibers still running after an instance of fiber_specific_ptr has been destroyed do not call any member functions on that instance. Member function get() T* get() const; Returns: The pointer associated with the current fiber. Throws: Nothing. The initial value associated with an instance of fiber_specific_ptr is NULL for each fiber. Member function operator->() T* operator->() const; Returns: this->get() Throws: Nothing. Member function operator*() T& operator*() const; Requires: this->get is not NULL. Returns: *(this->get()) Throws: Nothing. Member function release() T* release(); Effects: Return this->get() and store NULL as the pointer associated with the current fiber without invoking the cleanup function. Postcondition: this->get()==0 Throws: Nothing. Member function reset() void reset(T* new_value=0); Effects: If this->get()!=new_value and this->get() is non-NULL, invoke delete this->get() or fn(this->get()) as appropriate. Store new_value as the pointer associated with the current fiber. Postcondition: this->get()==new_value Throws: fiber_resource_error if an error occurs.
<link linkend="fiber.asio">Asynchronous network I/O (boost.asio)</link> In the past, code using asio's asynchronous operations was scattered by callbacks. Boost.Asio provides with its new asynchronous result feature a new way to simplify the code and make it easier to read. boost::asio::yield_context internally uses Boost.Coroutine: void echo(boost::asio::ip::tcp::socket& socket,boost::asio::yield_context yield){ char data[128]; // read asynchronous data from socket // execution context will be suspended until // some bytes are read from socket std::size_t n=socket.async_read_some(boost::asio::buffer(data),yield); // write some bytes asynchronously boost::asio::async_write(socket,boost::asio::buffer(data,n),yield); } Unfortunately Boost.Coroutine (boost::asio::yield_context) does not provide primitives to synchronize different coroutines (execution contexts). Boost.Fiber provides an integration of fibers into Boost.Asio so that asynchronous operations from Boost.Asio can be used together with fibers, synchronized by primitives provided by Boost.Fiber. The example section contains a complete publish-subscribe application demonstrating the use of fibers with asio's asynchronous operations. boost::fibers::asio::yield_context abstracts the fiber in asio's context. void subscriber::run( boost::fibers::asio::yield_fiber yield) { boost::system::error_code ec; // read first message == channel name std::string channel; boost::asio::async_read( socket_, boost::asio::buffer( channel), yield[ec]); if ( ec) throw std::runtime_error("no channel from subscriber"); // register new channel reg_.subscribe( channel, shared_from_this() ); for (;;) { boost::fibers::mutex::scoped_lock lk( mtx_); // wait for published messages // fiber gets suspended and will be woken up if a // new message has to be published to subscriber cond_.wait( lk); // '<fini>' terminates subscriber // data_ is a private member of subscriber and // gets filled by the publisher // notification of available data via condition_var cond_ if ( "<fini>" == std::string( data_) ) break; // write message asynchronously to subscriber // fiber gets suspended until message was written boost::asio::async_write( socket_, boost::asio::buffer( data_, max_length), yield[ec]); if ( ec) throw std::runtime_error("publishing message failed"); } } C10K problem The C10K-website 'The C10K problem', Dan Kegel from Dan Kegel describes the problem of handling ten thousand clients simultaneously and which strategies are possible. Boost.Fiber and Boost.Asio support the strategy 'serve many clients with each server thread, and use asynchronous I/O' without scattering the logic across many callbacks (as was asio's previous strategy) and overloading the operating system with too many threads. (Beyond a certain number of threads, the overhead of the kernel scheduler starts to swamp the available cores.) Because Boost.Fiber contains synchronization primitives, it is easy to synchronize different fibers and use asynchronous network I/O at the same time. Boost.Fiber provides the same classes and interfaces as Boost.Thread. Therefore developers are able to use patterns familiar from multi-threaded programming. For instance the strategy 'serve one client with one thread' could be transformed into 'serve one client with one fiber'.
<link linkend="fiber.acknowledgements">Acknowledgments</link> I'd like to thank Yuriy Krasnoschek and especially Nat Goodspeed.