]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | [/ |
2 | (C) Copyright 2007-8 Anthony Williams. | |
3 | (C) Copyright 2013 Oliver Kowalke. | |
4 | Distributed under the Boost Software License, Version 1.0. | |
5 | (See accompanying file LICENSE_1_0.txt or copy at | |
6 | http://www.boost.org/LICENSE_1_0.txt). | |
7 | ] | |
8 | ||
9 | [section:conditions Condition Variables] | |
10 | ||
11 | [heading Synopsis] | |
12 | ||
13 | enum class cv_status; { | |
14 | no_timeout, | |
15 | timeout | |
16 | }; | |
17 | ||
18 | class condition_variable; | |
19 | class condition_variable_any; | |
20 | ||
21 | The class [class_link condition_variable] provides a mechanism for a fiber to | |
22 | wait for notification from another fiber. When the fiber awakens from the | |
23 | wait, then it checks to see if the appropriate condition is now true, and | |
24 | continues if so. If the condition is not true, then the fiber calls `wait` | |
25 | again to resume waiting. In the simplest case, this condition is just a | |
26 | boolean variable: | |
27 | ||
28 | boost::fibers::condition_variable cond; | |
29 | boost::fibers::mutex mtx; | |
30 | bool data_ready = false; | |
31 | ||
32 | void process_data(); | |
33 | ||
34 | void wait_for_data_to_process() { | |
35 | { | |
36 | std::unique_lock< boost::fibers::mutex > lk( mtx); | |
37 | while ( ! data_ready) { | |
38 | cond.wait( lk); | |
39 | } | |
40 | } // release lk | |
41 | process_data(); | |
42 | } | |
43 | ||
44 | Notice that the `lk` is passed to [member_link condition_variable..wait]: | |
45 | `wait()` will atomically add the fiber to the set of fibers waiting on the | |
46 | condition variable, and unlock the [class_link mutex]. When the fiber is | |
47 | awakened, the `mutex` will be locked again before the call to `wait()` | |
48 | returns. This allows other fibers to acquire the `mutex` in order to update | |
49 | the shared data, and ensures that the data associated with the condition is | |
50 | correctly synchronized. | |
51 | ||
52 | `wait_for_data_to_process()` could equivalently be written: | |
53 | ||
54 | void wait_for_data_to_process() { | |
55 | { | |
56 | std::unique_lock< boost::fibers::mutex > lk( mtx); | |
57 | // make condition_variable::wait() perform the loop | |
58 | cond.wait( lk, [](){ return data_ready; }); | |
59 | } // release lk | |
60 | process_data(); | |
61 | } | |
62 | ||
63 | In the meantime, another fiber sets `data_ready` to `true`, and then calls | |
64 | either [member_link condition_variable..notify_one] or [member_link | |
65 | condition_variable..notify_all] on the [class_link condition_variable] `cond` | |
66 | to wake one waiting fiber or all the waiting fibers respectively. | |
67 | ||
68 | void retrieve_data(); | |
69 | void prepare_data(); | |
70 | ||
71 | void prepare_data_for_processing() { | |
72 | retrieve_data(); | |
73 | prepare_data(); | |
74 | { | |
75 | std::unique_lock< boost::fibers::mutex > lk( mtx); | |
76 | data_ready = true; | |
77 | } | |
78 | cond.notify_one(); | |
79 | } | |
80 | ||
81 | Note that the same [class_link mutex] is locked before the shared data is | |
82 | updated, but that the `mutex` does not have to be locked across the call to | |
83 | [member_link condition_variable..notify_one]. | |
84 | ||
85 | Locking is important because the synchronization objects provided by | |
86 | __boost_fiber__ can be used to synchronize fibers running on different | |
87 | threads. | |
88 | ||
89 | __boost_fiber__ provides both [class_link condition_variable] and [class_link | |
90 | condition_variable_any]. `boost::fibers::condition_variable` can only wait on | |
91 | __unique_lock__`< boost::fibers::`[class_link mutex]` >` while | |
92 | `boost::fibers::condition_variable_any` can wait on user-defined lock types. | |
93 | ||
94 | [#condition_variable_spurious_wakeups] | |
95 | [heading No Spurious Wakeups] | |
96 | ||
97 | Neither [class_link condition_variable] nor [class_link | |
98 | condition_variable_any] are subject to spurious wakeup: | |
99 | [member_link condition_variable..wait] can only wake up when | |
100 | [member_link condition_variable..notify_one] or | |
101 | [member_link condition_variable..notify_all] is called. Even so, it is prudent | |
102 | to use one of the `wait( lock, predicate )` overloads. | |
103 | ||
104 | Consider a set of consumer fibers processing items from a | |
105 | [@http://en.cppreference.com/w/cpp/container/queue `std::queue`]. The queue is | |
106 | continually populated by a set of producer fibers. | |
107 | ||
108 | The consumer fibers might reasonably wait on a `condition_variable` as long as | |
109 | the queue remains [@http://en.cppreference.com/w/cpp/container/queue/empty | |
110 | `empty()`]. | |
111 | ||
112 | Because producer fibers might | |
113 | [@http://en.cppreference.com/w/cpp/container/queue/push `push()`] items to the | |
114 | queue in bursts, they call [member_link condition_variable..notify_all] rather | |
115 | than [member_link condition_variable..notify_one]. | |
116 | ||
117 | But a given consumer fiber might well wake up from [member_link | |
118 | condition_variable..wait] and find the queue `empty()`, because other consumer | |
119 | fibers might already have processed all pending items. | |
120 | ||
121 | (See also [link spurious_wakeup spurious wakeup].) | |
122 | ||
123 | [#class_cv_status] | |
124 | [heading Enumeration `cv_status`] | |
125 | ||
126 | A timed wait operation might return because of timeout or not. | |
127 | ||
128 | enum class cv_status { | |
129 | no_timeout, | |
130 | timeout | |
131 | }; | |
132 | ||
133 | [heading `no_timeout`] | |
134 | [variablelist | |
135 | [[Effects:] [The condition variable was awakened with `notify_one` or `notify_all`.]] | |
136 | ] | |
137 | ||
138 | [heading `timeout`] | |
139 | [variablelist | |
140 | [[Effects:] [The condition variable was awakened by timeout.]] | |
141 | ] | |
142 | ||
143 | [/ The documentation for condition_variable_any and condition_variable is so | |
144 | nearly identical that we define a QuickBook template to capture its | |
145 | essentials. Differences are: | |
146 | the classname (of course), | |
147 | the locktype (a LockType template param vs. std::unique_lock<mutex>), | |
148 | the template parameter 'typename LockType', | |
149 | and -- for when it's the only template parameter -- the return type plus | |
150 | the template prefix 'template <typename LockType> void'. | |
151 | The last two really want to just vanish for condition_variable, but since | |
152 | you can't pass empty parameters to a QuickBook template (why??), the | |
153 | template_rtype param must supply the return type as well as the template <> | |
154 | clause, and the template_arg parameter must supply the 'typename' for the | |
155 | next template parameter as well as 'typename LockType'.] | |
156 | [template condition_variable_x[classname locktype template_rtype template_arg] | |
157 | [class_heading [classname]] | |
158 | ||
159 | #include <boost/fiber/condition_variable.hpp> | |
160 | ||
161 | namespace boost { | |
162 | namespace fibers { | |
163 | ||
164 | class ``[classname]`` { | |
165 | public: | |
166 | ``[classname]``(); | |
167 | ~``[classname]``(); | |
168 | ||
169 | ``[classname]``( ``[classname]`` const&) = delete; | |
170 | ``[classname]`` & operator=( ``[classname]`` const&) = delete; | |
171 | ||
172 | void notify_one() noexcept; | |
173 | void notify_all() noexcept; | |
174 | ||
175 | ``[template_rtype]`` wait( ``[locktype]`` &); | |
176 | ||
177 | template< ``[template_arg]`` Pred > | |
178 | void wait( ``[locktype]`` &, Pred); | |
179 | ||
180 | template< ``[template_arg]`` Clock, typename Duration > | |
181 | cv_status wait_until( ``[locktype]`` &, | |
182 | std::chrono::time_point< Clock, Duration > const&); | |
183 | ||
184 | template< ``[template_arg]`` Clock, typename Duration, typename Pred > | |
185 | bool wait_until( ``[locktype]`` &, | |
186 | std::chrono::time_point< Clock, Duration > const&, | |
187 | Pred); | |
188 | ||
189 | template< ``[template_arg]`` Rep, typename Period > | |
190 | cv_status wait_for( ``[locktype]`` &, | |
191 | std::chrono::duration< Rep, Period > const&); | |
192 | ||
193 | template< ``[template_arg]`` Rep, typename Period, typename Pred > | |
194 | bool wait_for( ``[locktype]`` &, | |
195 | std::chrono::duration< Rep, Period > const&, | |
196 | Pred); | |
197 | }; | |
198 | ||
199 | }} | |
200 | ||
201 | [heading Constructor] | |
202 | ||
203 | ``[classname]``() | |
204 | ||
205 | [variablelist | |
206 | [[Effects:] [Creates the object.]] | |
207 | [[Throws:] [Nothing.]] | |
208 | ] | |
209 | ||
210 | [heading Destructor] | |
211 | ||
212 | ~``[classname]``() | |
213 | ||
214 | [variablelist | |
215 | [[Precondition:] [All fibers waiting on `*this` have been notified by a call to | |
216 | `notify_one` or `notify_all` (though the respective calls to `wait`, `wait_for` or | |
217 | `wait_until` need not have returned).]] | |
218 | [[Effects:] [Destroys the object.]] | |
219 | ] | |
220 | ||
221 | [member_heading [classname]..notify_one] | |
222 | ||
223 | void notify_one() noexcept; | |
224 | ||
225 | [variablelist | |
226 | [[Effects:] [If any fibers are currently __blocked__ waiting on `*this` in a | |
227 | call to `wait`, `wait_for` or `wait_until`, unblocks one of those fibers.]] | |
228 | [[Throws:] [Nothing.]] | |
229 | [[Note:] [It is arbitrary which waiting fiber is resumed.]] | |
230 | ] | |
231 | ||
232 | [member_heading [classname]..notify_all] | |
233 | ||
234 | void notify_all() noexcept; | |
235 | ||
236 | [variablelist | |
237 | [[Effects:] [If any fibers are currently __blocked__ waiting on `*this` in a | |
238 | call to `wait`, `wait_for` or `wait_until`, unblocks all of those fibers.]] | |
239 | [[Throws:] [Nothing.]] | |
240 | [[Note:] [This is why a waiting fiber must ['also] check for the desired | |
241 | program state using a mechanism external to the [`[classname]], and | |
242 | retry the wait until that state is reached. A fiber waiting on a | |
243 | [`[classname]] might well wake up a number of times before the desired | |
244 | state is reached.]] | |
245 | ] | |
246 | ||
247 | [template_member_heading [classname]..wait] | |
248 | ||
249 | ``[template_rtype]`` wait( ``[locktype]`` & lk); | |
250 | ||
251 | template< ``[template_arg]`` Pred > | |
252 | void wait( ``[locktype]`` & lk, Pred pred); | |
253 | ||
254 | [variablelist | |
255 | [[Precondition:] [`lk` is locked by the current fiber, and either no other | |
256 | fiber is currently waiting on `*this`, or the execution of the | |
257 | [@http://en.cppreference.com/w/cpp/thread/unique_lock/mutex `mutex()`] | |
258 | member function on the `lk` objects supplied in the calls to `wait` in all the | |
259 | fibers currently waiting on `*this` would return the same value as | |
260 | `lk->mutex()` for this call to `wait`.]] | |
261 | [[Effects:] [Atomically call `lk.unlock()` and blocks the current fiber. The | |
262 | fiber will unblock when notified by a call to `this->notify_one()` or | |
263 | `this->notify_all()`. When the fiber is unblocked (for whatever | |
264 | reason), the lock is reacquired by invoking `lk.lock()` before the call to | |
265 | `wait` returns. The lock is also reacquired by invoking `lk.lock()` if the | |
266 | function exits with an exception. | |
267 | The member function accepting `pred` is shorthand for: `` | |
268 | ||
269 | while ( ! pred() ) { | |
270 | wait( lk); | |
271 | } | |
272 | ``]] | |
273 | [[Postcondition:] [`lk` is locked by the current fiber.]] | |
274 | [[Throws:] [__fiber_error__ if an error occurs.]] | |
275 | [[Note:] [The Precondition is a bit dense. It merely states that all the | |
276 | fibers concurrently calling `wait` on `*this` must wait on `lk` objects | |
277 | governing the ['same] [class_link mutex]. Three distinct objects are involved | |
278 | in any [`[classname]::wait()] call: the [`[classname]] itself, the `mutex` | |
279 | coordinating access between fibers and a local lock object (e.g. | |
280 | __unique_lock__). In general, you can partition the lifespan of a given | |
281 | [`[classname]] instance into periods with one or more fibers waiting on it, | |
282 | separated by periods when no fibers are waiting on it. When more than one | |
283 | fiber is waiting on that [`[classname]], all must pass lock objects | |
284 | referencing the ['same] `mutex` instance.]] | |
285 | ] | |
286 | ||
287 | [template_member_heading [classname]..wait_until] | |
288 | ||
289 | template< ``[template_arg]`` Clock, typename Duration > | |
290 | cv_status wait_until( ``[locktype]`` & lk, | |
291 | std::chrono::time_point< Clock, Duration > const& abs_time); | |
292 | ||
293 | template< ``[template_arg]`` Clock, typename Duration, typename Pred > | |
294 | bool wait_until( ``[locktype]`` & lk, | |
295 | std::chrono::time_point< Clock, Duration > const& abs_time, | |
296 | Pred pred); | |
297 | ||
298 | [variablelist | |
299 | [[Precondition:] [`lk` is locked by the current fiber, and either no other | |
300 | fiber is currently waiting on `*this`, or the execution of the `mutex()` member | |
301 | function on the `lk` objects supplied in the calls to `wait`, `wait_for` or | |
302 | `wait_until` in all the fibers currently waiting on `*this` would return the | |
303 | same value as `lk.mutex()` for this call to `wait_until`.]] | |
304 | [[Effects:] [Atomically call `lk.unlock()` and blocks the current fiber. The | |
305 | fiber will unblock when notified by a call to `this->notify_one()` or | |
306 | `this->notify_all()`, when the system time | |
307 | would be equal to or later than the specified `abs_time`. | |
308 | When the fiber is unblocked (for whatever reason), the lock is reacquired by | |
309 | invoking `lk.lock()` before the call to `wait_until` returns. The lock is also | |
310 | reacquired by invoking `lk.lock()` if the function exits with an exception. | |
311 | The member function accepting `pred` is shorthand for: `` | |
312 | ||
313 | while ( ! pred() ) { | |
314 | if ( cv_status::timeout == wait_until( lk, abs_time) ) | |
315 | return pred(); | |
316 | } | |
317 | return true; | |
318 | ||
319 | `` That is, even if `wait_until()` times out, it can still return `true` if | |
320 | `pred()` returns `true` at that time.]] | |
321 | [[Postcondition:] [`lk` is locked by the current fiber.]] | |
322 | [[Throws:] [__fiber_error__ if an error | |
323 | occurs or timeout-related exceptions.]] | |
324 | [[Returns:] [The overload without `pred` returns `cv_status::no_timeout` if | |
325 | awakened by `notify_one()` or `notify_all()`, or `cv_status::timeout` if | |
326 | awakened because the system time is past `abs_time`.]] | |
327 | [[Returns:] [The overload accepting `pred` returns `false` if the call is | |
328 | returning because the time specified by `abs_time` was reached and the | |
329 | predicate returns `false`, `true` otherwise.]] | |
330 | [[Note:] [See [*Note] for [member_link [classname]..wait].]] | |
331 | ] | |
332 | ||
333 | [template_member_heading [classname]..wait_for] | |
334 | ||
335 | template< ``[template_arg]`` Rep, typename Period > | |
336 | cv_status wait_for( ``[locktype]`` & lk, | |
337 | std::chrono::duration< Rep, Period > const& rel_time); | |
338 | ||
339 | template< ``[template_arg]`` Rep, typename Period, typename Pred > | |
340 | bool wait_for( ``[locktype]`` & lk, | |
341 | std::chrono::duration< Rep, Period > const& rel_time, | |
342 | Pred pred); | |
343 | ||
344 | [variablelist | |
345 | [[Precondition:] [`lk` is locked by the current fiber, and either no other | |
346 | fiber is currently waiting on `*this`, or the execution of the `mutex()` member | |
347 | function on the `lk` objects supplied in the calls to `wait`, `wait_for` or | |
348 | `wait_until` in all the fibers currently waiting on `*this` would return the | |
349 | same value as `lk.mutex()` for this call to `wait_for`.]] | |
350 | [[Effects:] [Atomically call `lk.unlock()` and blocks the current fiber. The | |
351 | fiber will unblock when notified by a call to `this->notify_one()` or | |
352 | `this->notify_all()`, when a time interval equal to or greater than the | |
353 | specified `rel_time` has elapsed. When the fiber is | |
354 | unblocked (for whatever reason), the lock is reacquired by invoking | |
355 | `lk.lock()` before the call to `wait` returns. The lock is also reacquired by | |
356 | invoking `lk.lock()` if the function exits with an exception. | |
357 | The `wait_for()` member function accepting `pred` is shorthand for: `` | |
358 | ||
359 | while ( ! pred() ) { | |
360 | if ( cv_status::timeout == wait_for( lk, rel_time) ) { | |
361 | return pred(); | |
362 | } | |
363 | } | |
364 | return true; | |
365 | ||
366 | `` (except of course that `rel_time` is adjusted for each iteration). | |
367 | The point is that, even if `wait_for()` times out, it can still return `true` | |
368 | if `pred()` returns `true` at that time.]] | |
369 | [[Postcondition:] [`lk` is locked by the current fiber.]] | |
370 | [[Throws:] [__fiber_error__ if an error | |
371 | occurs or timeout-related exceptions.]] | |
372 | [[Returns:] [The overload without `pred` returns `cv_status::no_timeout` if | |
373 | awakened by `notify_one()` or `notify_all()`, or `cv_status::timeout` if | |
374 | awakened because at least `rel_time` has elapsed.]] | |
375 | [[Returns:] [The overload accepting `pred` returns `false` if the call is | |
376 | returning because at least `rel_time` has elapsed and the predicate | |
377 | returns `false`, `true` otherwise.]] | |
378 | [[Note:] [See [*Note] for [member_link [classname]..wait].]] | |
379 | ] | |
380 | ] | |
381 | [/ The above is the template describing both condition_variable_any and | |
382 | condition_variable. We must invoke it to generate those descriptions. First | |
383 | condition_variable_any, which accepts any LockType. Because a QuickBook | |
384 | template argument cannot be empty, the template<> prefix must also supply | |
385 | the 'void' return type for wait(); likewise the 'typename LockType' | |
386 | template argument must also supply the following 'typename'.] | |
387 | [condition_variable_x condition_variable_any..LockType..template< typename LockType > | |
388 | void..typename LockType, typename] | |
389 | [/ Now condition_variable, which accepts specifically std::unique_lock<mutex>. | |
390 | Make the template<> prefix for wait() go away by supplying only its return | |
391 | type 'void'; make the 'typename LockType' template argument go away by | |
392 | supplying only the following 'typename'.] | |
393 | [condition_variable_x condition_variable..std::unique_lock< mutex >..void..typename] | |
394 | ||
395 | [endsect] |