123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- [/
- (C) Copyright 2007-8 Anthony Williams.
- (C) Copyright 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).
- ]
- [section:conditions Condition Variables]
- [heading Synopsis]
- enum class cv_status; {
- no_timeout,
- timeout
- };
- class condition_variable;
- class condition_variable_any;
- The class [class_link condition_variable] provides a mechanism for a fiber to
- wait for notification from another fiber. 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 = false;
- void process_data();
- void wait_for_data_to_process() {
- {
- std::unique_lock< boost::fibers::mutex > lk( mtx);
- while ( ! data_ready) {
- cond.wait( lk);
- }
- } // release lk
- process_data();
- }
- Notice that the `lk` is passed to [member_link condition_variable..wait]:
- `wait()` will atomically add the fiber to the set of fibers waiting on the
- condition variable, and unlock the [class_link 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.
- `wait_for_data_to_process()` could equivalently be written:
- void wait_for_data_to_process() {
- {
- std::unique_lock< boost::fibers::mutex > lk( mtx);
- // make condition_variable::wait() perform the loop
- cond.wait( lk, [](){ return data_ready; });
- } // release lk
- process_data();
- }
- In the meantime, another fiber sets `data_ready` to `true`, and then calls
- either [member_link condition_variable..notify_one] or [member_link
- condition_variable..notify_all] on the [class_link condition_variable] `cond`
- 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();
- {
- std::unique_lock< boost::fibers::mutex > lk( mtx);
- data_ready = true;
- }
- cond.notify_one();
- }
- Note that the same [class_link mutex] is locked before the shared data is
- updated, but that the `mutex` does not have to be locked across the call to
- [member_link condition_variable..notify_one].
- Locking is important because the synchronization objects provided by
- __boost_fiber__ can be used to synchronize fibers running on different
- threads.
- __boost_fiber__ provides both [class_link condition_variable] and [class_link
- condition_variable_any]. `boost::fibers::condition_variable` can only wait on
- __unique_lock__`< boost::fibers::`[class_link mutex]` >` while
- `boost::fibers::condition_variable_any` can wait on user-defined lock types.
- [#condition_variable_spurious_wakeups]
- [heading No Spurious Wakeups]
- Neither [class_link condition_variable] nor [class_link
- condition_variable_any] are subject to spurious wakeup:
- [member_link condition_variable..wait] can only wake up when
- [member_link condition_variable..notify_one] or
- [member_link condition_variable..notify_all] is called. Even so, it is prudent
- to use one of the `wait( lock, predicate )` overloads.
- Consider a set of consumer fibers processing items from a
- [@http://en.cppreference.com/w/cpp/container/queue `std::queue`]. The queue is
- continually populated by a set of producer fibers.
- The consumer fibers might reasonably wait on a `condition_variable` as long as
- the queue remains [@http://en.cppreference.com/w/cpp/container/queue/empty
- `empty()`].
- Because producer fibers might
- [@http://en.cppreference.com/w/cpp/container/queue/push `push()`] items to the
- queue in bursts, they call [member_link condition_variable..notify_all] rather
- than [member_link condition_variable..notify_one].
- But a given consumer fiber might well wake up from [member_link
- condition_variable..wait] and find the queue `empty()`, because other consumer
- fibers might already have processed all pending items.
- (See also [link spurious_wakeup spurious wakeup].)
- [#class_cv_status]
- [heading Enumeration `cv_status`]
- A timed wait operation might return because of timeout or not.
- enum class cv_status {
- no_timeout,
- timeout
- };
- [heading `no_timeout`]
- [variablelist
- [[Effects:] [The condition variable was awakened with `notify_one` or `notify_all`.]]
- ]
- [heading `timeout`]
- [variablelist
- [[Effects:] [The condition variable was awakened by timeout.]]
- ]
- [/ The documentation for condition_variable_any and condition_variable is so
- nearly identical that we define a QuickBook template to capture its
- essentials. Differences are:
- the classname (of course),
- the locktype (a LockType template param vs. std::unique_lock<mutex>),
- the template parameter 'typename LockType',
- and -- for when it's the only template parameter -- the return type plus
- the template prefix 'template <typename LockType> void'.
- The last two really want to just vanish for condition_variable, but since
- you can't pass empty parameters to a QuickBook template (why??), the
- template_rtype param must supply the return type as well as the template <>
- clause, and the template_arg parameter must supply the 'typename' for the
- next template parameter as well as 'typename LockType'.]
- [template condition_variable_x[classname locktype template_rtype template_arg]
- [class_heading [classname]]
- #include <boost/fiber/condition_variable.hpp>
- namespace boost {
- namespace fibers {
- class ``[classname]`` {
- public:
- ``[classname]``();
- ~``[classname]``();
- ``[classname]``( ``[classname]`` const&) = delete;
- ``[classname]`` & operator=( ``[classname]`` const&) = delete;
- void notify_one() noexcept;
- void notify_all() noexcept;
- ``[template_rtype]`` wait( ``[locktype]`` &);
- template< ``[template_arg]`` Pred >
- void wait( ``[locktype]`` &, Pred);
- template< ``[template_arg]`` Clock, typename Duration >
- cv_status wait_until( ``[locktype]`` &,
- std::chrono::time_point< Clock, Duration > const&);
- template< ``[template_arg]`` Clock, typename Duration, typename Pred >
- bool wait_until( ``[locktype]`` &,
- std::chrono::time_point< Clock, Duration > const&,
- Pred);
- template< ``[template_arg]`` Rep, typename Period >
- cv_status wait_for( ``[locktype]`` &,
- std::chrono::duration< Rep, Period > const&);
- template< ``[template_arg]`` Rep, typename Period, typename Pred >
- bool wait_for( ``[locktype]`` &,
- std::chrono::duration< Rep, Period > const&,
- Pred);
- };
- }}
- [heading Constructor]
- ``[classname]``()
- [variablelist
- [[Effects:] [Creates the object.]]
- [[Throws:] [Nothing.]]
- ]
- [heading Destructor]
- ~``[classname]``()
- [variablelist
- [[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.]]
- ]
- [member_heading [classname]..notify_one]
- void notify_one() noexcept;
- [variablelist
- [[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_heading [classname]..notify_all]
- void notify_all() noexcept;
- [variablelist
- [[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 [`[classname]], and
- retry the wait until that state is reached. A fiber waiting on a
- [`[classname]] might well wake up a number of times before the desired
- state is reached.]]
- ]
- [template_member_heading [classname]..wait]
- ``[template_rtype]`` wait( ``[locktype]`` & lk);
- template< ``[template_arg]`` Pred >
- void wait( ``[locktype]`` & lk, Pred pred);
- [variablelist
- [[Precondition:] [`lk` is locked by the current fiber, and either no other
- fiber is currently waiting on `*this`, or the execution of the
- [@http://en.cppreference.com/w/cpp/thread/unique_lock/mutex `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()`. 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_error__ if an error occurs.]]
- [[Note:] [The Precondition is a bit dense. It merely states that all the
- fibers concurrently calling `wait` on `*this` must wait on `lk` objects
- governing the ['same] [class_link mutex]. Three distinct objects are involved
- in any [`[classname]::wait()] call: the [`[classname]] itself, the `mutex`
- coordinating access between fibers and a local lock object (e.g.
- __unique_lock__). In general, you can partition the lifespan of a given
- [`[classname]] instance into periods with one or more fibers waiting on it,
- separated by periods when no fibers are waiting on it. When more than one
- fiber is waiting on that [`[classname]], all must pass lock objects
- referencing the ['same] `mutex` instance.]]
- ]
- [template_member_heading [classname]..wait_until]
- template< ``[template_arg]`` Clock, typename Duration >
- cv_status wait_until( ``[locktype]`` & lk,
- std::chrono::time_point< Clock, Duration > const& abs_time);
- template< ``[template_arg]`` Clock, typename Duration, typename Pred >
- bool wait_until( ``[locktype]`` & lk,
- std::chrono::time_point< Clock, Duration > const& abs_time,
- Pred pred);
- [variablelist
- [[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_until`.]]
- [[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 system time
- would be equal to or later than the specified `abs_time`.
- When the fiber is unblocked (for whatever reason), the lock is reacquired by
- invoking `lk.lock()` before the call to `wait_until` 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, abs_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_error__ if an error
- occurs or timeout-related exceptions.]]
- [[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 the system time is past `abs_time`.]]
- [[Returns:] [The overload accepting `pred` returns `false` if the call is
- returning because the time specified by `abs_time` was reached and the
- predicate returns `false`, `true` otherwise.]]
- [[Note:] [See [*Note] for [member_link [classname]..wait].]]
- ]
- [template_member_heading [classname]..wait_for]
- template< ``[template_arg]`` Rep, typename Period >
- cv_status wait_for( ``[locktype]`` & lk,
- std::chrono::duration< Rep, Period > const& rel_time);
- template< ``[template_arg]`` Rep, typename Period, typename Pred >
- bool wait_for( ``[locktype]`` & lk,
- std::chrono::duration< Rep, Period > const& rel_time,
- Pred pred);
- [variablelist
- [[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_for`.]]
- [[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 `rel_time` has elapsed. 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 `wait_for()` member function accepting `pred` is shorthand for: ``
- while ( ! pred() ) {
- if ( cv_status::timeout == wait_for( lk, rel_time) ) {
- return pred();
- }
- }
- return true;
- `` (except of course that `rel_time` is adjusted for each iteration).
- The point is that, 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_error__ if an error
- occurs or timeout-related exceptions.]]
- [[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 `rel_time` has elapsed.]]
- [[Returns:] [The overload accepting `pred` returns `false` if the call is
- returning because at least `rel_time` has elapsed and the predicate
- returns `false`, `true` otherwise.]]
- [[Note:] [See [*Note] for [member_link [classname]..wait].]]
- ]
- ]
- [/ The above is the template describing both condition_variable_any and
- condition_variable. We must invoke it to generate those descriptions. First
- condition_variable_any, which accepts any LockType. Because a QuickBook
- template argument cannot be empty, the template<> prefix must also supply
- the 'void' return type for wait(); likewise the 'typename LockType'
- template argument must also supply the following 'typename'.]
- [condition_variable_x condition_variable_any..LockType..template< typename LockType >
- void..typename LockType, typename]
- [/ Now condition_variable, which accepts specifically std::unique_lock<mutex>.
- Make the template<> prefix for wait() go away by supplying only its return
- type 'void'; make the 'typename LockType' template argument go away by
- supplying only the following 'typename'.]
- [condition_variable_x condition_variable..std::unique_lock< mutex >..void..typename]
- [endsect]
|