1414 * One of the template parameters is the container type, allowing customization
1515 * for specific use cases (see below for additional details).
1616 *
17- * If the @c close method is called, all reader threads calling @c wait_and_pop
17+ * A @c std::stop_token can be passed in through the constructors, which allows
18+ * aa external @c std::stop_source to @c request_stop. Alternatively, an
19+ * internal @c stop_token will be used, allowing the @c wait_queue
20+ * @c request_stop method to be used to shutdown @c wait_queue processing.
21+ *
22+ * Once a @c request_stop is called (either externally or through the @c wait_queue
23+ * @c request_stop) all reader threads calling @c wait_and_pop
1824 * are notified, and an empty value returned to those threads. Subsequent calls
1925 * to @c push will return a @c false value.
2026 *
2733 * wq.push(42);
2834 * ...
2935 * // all finished, time to shutdown
30- * wq.close ();
36+ * wq.request_stop ();
3137 *
3238 * // inside reader thread, assume wq passed in by reference
3339 * auto rtn_val = wq.wait_and_pop(); // return type is std::optional<int>
34- * if (!rtn_val) { // empty value, close has been called
35- * // time to end reader thread
40+ * if (!rtn_val) { // empty value, request_stop has been called
41+ * // time to exit reader thread
3642 * }
3743 * if (*rtn_val == 42) ...
3844 * @endcode
4753 * @endcode
4854 *
4955 * The container type must support the following (depending on which
50- * methods are instantiated ): default construction, construction from a
56+ * methods are called ): default construction, construction from a
5157 * begin and end iterator, construction with an initial size,
5258 * @c push_back (preferably overloaded for both copy and move),
5359 * @c emplace_back (with a template parameter pack), @c front, @c pop_front,
5460 * @c empty, and @c size. The container must also have a @c size_type
5561 * defined.
5662 *
5763 * This class is based on code from the book Concurrency in Action by Anthony
58- * Williams. The core logic in this class is the same as provided by Anthony
59- * in his book, but the interfaces have changed and additional features added.
60- * The name of the utility class template in Anthony's book is @c threadsafe_queue.
64+ * Williams (2012 edition). The core logic in this class is the same as provided
65+ * by Anthony in his book, but the class interface is changed and additional
66+ * features added. In particular, @ std::stop_token and @c condition_variable_any
67+ * are C++ 20 features not in Anthony's original code. The name of the utility
68+ * class template in Anthony's book is @c threadsafe_queue.
6169 *
6270 * @note A fixed size buffer can be used for the container, which eliminates
6371 * queue memory management happening during a @c push or @c pop. In particular,
64- * the proposed @c std::ring_span container (C++ 20, most likely) works well
65- * for this use case, and this code has been tested with @c ring-span lite from
72+ * the proposed @c std::ring_span container works well
73+ * for this use case, and this code has been tested with @c ring-span- lite from
6674 * Martin Moene. The constructor that takes an iterator range can be used for
6775 * a container view type, which means that the @c wait_queue owns and manages
6876 * a view rather than the underlying container buffer.
6977 *
7078 * @note The @c boost @c circular_buffer can be used for the container type. Memory is
7179 * allocated only once, at container construction time. This may be useful for
72- * environments where construction can be dynamic, but a @c push or @c pop must
73- * not allocate or deallocate memory.
80+ * environments where construction can use dynamic memory but a @c push or @c pop
81+ * must not allocate or deallocate memory.
7482 *
7583 * @note If the container type is @c boost @c circular_buffer then the default
7684 * constructor for @c wait_queue cannot be used (since it would result in a container
7785 * with an empty capacity).
7886 *
79- * @note Iterators are not supported, due to obvious difficulties with maintaining
80- * consistency and integrity. The @c apply method can be used to access the internal
81- * data in a threadsafe manner.
87+ * @note Iterators to a @c wait_queue are not supported, due to obvious difficulties
88+ * with maintaining * consistency and integrity. The @c apply method can be used to
89+ * access the internal data in a threadsafe manner.
8290 *
8391 * @note Copy and move construction or assignment for the whole queue is
8492 * disallowed, since the use cases and underlying implications are not clear
108116#define WAIT_QUEUE_HPP_INCLUDED
109117
110118#include < deque>
111- #include < mutex>
119+ #include < mutex> // std::scoped_lock, std::mutex
112120#include < condition_variable>
113- #include < atomic >
121+ #include < stop_token > // std::stop_source, std::stop_token
114122#include < optional>
115123#include < utility> // std::move, std::move_if_noexcept
116124#include < type_traits> // for noexcept specs
@@ -120,12 +128,13 @@ namespace chops {
120128template <typename T, typename Container = std::deque<T> >
121129class wait_queue {
122130private:
123- mutable std::mutex m_mut;
124- std::condition_variable m_data_cond;
125- std::atomic_bool m_closed;
126- Container m_data_queue;
131+ mutable std::mutex m_mut;
132+ std::optional<std::stop_source> m_stop_src;
133+ std::stop_token m_stop_tok;
134+ std::condition_variable_any m_data_cond;
135+ Container m_data_queue;
127136
128- using lock_guard = std::lock_guard <std::mutex>;
137+ using lock_guard = std::scoped_lock <std::mutex>;
129138
130139public:
131140
@@ -137,14 +146,30 @@ class wait_queue {
137146 /* *
138147 * @brief Default construct a @c wait_queue.
139148 *
149+ * An internal @c stop_source is used to provide a @c std::stop_token for
150+ * coordinating shutdown.
151+ *
140152 * @note A default constructed @c boost @c circular_buffer cannot do
141153 * anything, so a different @c wait_queue constructor must be used if
142154 * instantiated with a @c boost @c circular_buffer.
143155 *
144156 */
145157 wait_queue ()
146158 // noexcept(std::is_nothrow_constructible<Container>::value)
147- : m_mut(), m_data_cond(), m_closed(false ), m_data_queue() { }
159+ : m_mut(), m_stop_src(std::stop_source{}), m_stop_tok((*m_stop_src).get_token()),
160+ m_data_cond (), m_data_queue() { }
161+
162+ /* *
163+ * @brief Construct a @c wait_queue with an externally provided @c std::stop_token.
164+ *
165+ * @param stop_tok A @c std::stop_token which can be used to shutdown @c wait_queue
166+ * processing.
167+ *
168+ */
169+ wait_queue (std::stop_token stop_tok)
170+ // noexcept(std::is_nothrow_constructible<Container>::value)
171+ : m_mut(), m_stop_src(), m_stop_tok(stop_tok),
172+ m_data_cond (), m_data_queue() { }
148173
149174 /* *
150175 * @brief Construct a @c wait_queue with an iterator range for the container.
@@ -155,6 +180,9 @@ class wait_queue {
155180 * set accordingly. A @c ring_span, however, uses the range distance to define
156181 * a capacity and sets the initial size to zero.
157182 *
183+ * An internal @c std::stop_source is used to provide a @c std::stop_token for
184+ * coordinating shutdown.
185+ *
158186 * @note This is the only constructor that can be used with a @c ring_span
159187 * container type.
160188 *
@@ -165,14 +193,34 @@ class wait_queue {
165193 template <typename Iter>
166194 wait_queue (Iter beg, Iter end)
167195 // noexcept(std::is_nothrow_constructible<Container, Iter, Iter>::value)
168- : m_mut(), m_data_cond(), m_closed(false ), m_data_queue(beg, end) { }
196+ : m_mut(), m_stop_src(std::stop_source{}), m_stop_tok((*m_stop_src).get_token()),
197+ m_data_cond(), m_data_queue(beg, end) { }
198+
199+ /* *
200+ * @brief Construct a @c wait_queue with an iterator range and a @c std::stop_token.
201+ *
202+ * @param stop_tok A @c std::stop_token which can be used to shutdown @c wait_queue
203+ * processing.
204+ *
205+ * @param beg Beginning iterator.
206+ *
207+ * @param end Ending iterator.
208+ */
209+ template <typename Iter>
210+ wait_queue (std::stop_token stop_tok, Iter beg, Iter end)
211+ // noexcept(std::is_nothrow_constructible<Container, Iter, Iter>::value)
212+ : m_mut(), m_stop_src(), m_stop_tok(stop_tok),
213+ m_data_cond(), m_data_queue(beg, end) { }
169214
170215 /* *
171216 * @brief Construct a @c wait_queue with an initial size or capacity.
172217 *
173218 * Construct the container (or container view) with an initial size of default
174219 * inserted elements or with an initial capacity, depending on the container type.
175220 *
221+ * An internal @c std::stop_source is used to provide a @c std::stop_token for
222+ * coordinating shutdown.
223+ *
176224 * @note This constructor cannot be used with a @c ring_span container type.
177225 *
178226 * @note Using this constructor with a @c boost @c circular_buffer creates a
@@ -186,7 +234,23 @@ class wait_queue {
186234 */
187235 wait_queue (size_type sz)
188236 // noexcept(std::is_nothrow_constructible<Container, size_type>::value)
189- : m_mut(), m_data_cond(), m_closed(false ), m_data_queue(sz) { }
237+ : m_mut(), m_stop_src(std::stop_source{}), m_stop_tok((*m_stop_src).get_token()),
238+ m_data_cond(), m_data_queue(sz) { }
239+
240+ /* *
241+ * @brief Construct a @c wait_queue with an initial size or capacity along
242+ * with a @c std::stop_token.
243+ *
244+ * @param stop_tok A @c std::stop_token which can be used to shutdown @c wait_queue
245+ * processing.
246+ *
247+ * @param sz Capacity or initial size, depending on container type.
248+ *
249+ */
250+ wait_queue (std::stop_token stop_tok, size_type sz)
251+ // noexcept(std::is_nothrow_constructible<Container, size_type>::value)
252+ : m_mut(), m_stop_src(), m_stop_tok((*m_stop_src).get_token()),
253+ m_data_cond(), m_data_queue(sz) { }
190254
191255 // disallow copy or move construction of the entire object
192256 wait_queue (const wait_queue&) = delete;
@@ -199,24 +263,16 @@ class wait_queue {
199263 // modifying methods
200264
201265 /* *
202- * @brief Open a previously closed @c wait_queue for processing.
203- *
204- * @note The initial state of a @c wait_queue is open.
205- */
206- void open () noexcept {
207- m_closed = false ;
208- }
209-
210- /* *
211- * @brief Close a @c wait_queue for processing.
266+ * @brief Request the @c wait_queue to stop processing.
212267 *
213268 * When this method is called, all waiting reader threaders will be notified.
214269 * Subsequent @c push operations will return @c false.
215270 */
216- void close () /* noexcept */ {
217- m_closed = true ;
218- lock_guard lk{m_mut};
219- m_data_cond.notify_all ();
271+ bool request_stop () noexcept {
272+ if (m_stop_src) {
273+ return (*m_stop_src).request_stop ();
274+ }
275+ return false ;
220276 }
221277
222278 /* *
@@ -227,10 +283,11 @@ class wait_queue {
227283 *
228284 * @param val Val to copy into the queue.
229285 *
230- * @return @c true if successful, @c false if the @c wait_queue is closed.
286+ * @return @c true if successful, @c false if the @c wait_queue has been
287+ * requested to stop.
231288 */
232289 bool push (const T& val) /* noexcept(std::is_nothrow_copy_constructible<T>::value) */ {
233- if (m_closed ) {
290+ if (m_stop_tok. stop_requested () ) {
234291 return false ;
235292 }
236293 lock_guard lk{m_mut};
@@ -246,7 +303,7 @@ class wait_queue {
246303 * be moved (if possible) instead of copied.
247304 */
248305 bool push (T&& val) /* noexcept(std::is_nothrow_move_constructible<T>::value) */ {
249- if (m_closed ) {
306+ if (m_stop_tok. stop_requested () ) {
250307 return false ;
251308 }
252309 lock_guard lk{m_mut};
@@ -265,11 +322,12 @@ class wait_queue {
265322 * @c emplace method calls. @c emplace_push for a @c wait_queue does not follow this
266323 * convention and instead has the same return as the @c push methods.
267324 *
268- * @return @c true if successful, @c false if the @c wait_queue is closed.
325+ * @return @c true if successful, @c false if the @c wait_queue is has been requested
326+ * to stop.
269327 */
270328 template <typename ... Args>
271329 bool emplace_push (Args &&... args) /* noexcept(std::is_nothrow_constructible<T, Args...>::value)*/ {
272- if (m_closed ) {
330+ if (m_stop_tok. stop_requested () ) {
273331 return false ;
274332 }
275333 lock_guard lk{m_mut};
@@ -282,21 +340,17 @@ class wait_queue {
282340 * @brief Pop and return a value from the @c wait_queue, blocking and waiting for a writer
283341 * thread to push a value if one is not immediately available.
284342 *
285- * If this method is called after a @c wait_queue has been closed , an empty @c std::optional
286- * is returned. If a @c wait_queue needs to be flushed after it is closed, @c try_pop should
287- * be called instead.
343+ * If this method is called after a @c wait_queue has been requested to stop , an empty
344+ * @c std::optional is returned. If a @c wait_queue needs to be flushed after it is stopped,
345+ * @c try_pop should be called instead.
288346 *
289347 * @return A value from the @c wait_queue (if non-empty). If the @c std::optional is empty,
290- * the @c wait_queue has been closed .
348+ * the @c wait_queue has been requested to be stopped .
291349 */
292350 std::optional<T> wait_and_pop () /* noexcept(std::is_nothrow_constructible<T>::value) */ {
293- if (m_closed) {
294- return std::optional<T> {};
295- }
296351 std::unique_lock<std::mutex> lk{m_mut};
297- m_data_cond.wait ( lk, [this ] { return m_closed || !m_data_queue.empty (); } );
298- if (m_data_queue.empty ()) {
299- return std::optional<T> {}; // queue was closed, no data available
352+ if (!m_data_cond.wait ( lk, m_stop_tok, [this ] { return !m_data_queue.empty (); } )) {
353+ return std::optional<T> {}; // queue was request to stop, no data available
300354 }
301355 std::optional<T> val {std::move_if_noexcept (m_data_queue.front ())}; // move construct if possible
302356 m_data_queue.pop_front ();
@@ -311,6 +365,9 @@ class wait_queue {
311365 * available in the @c wait_queue.
312366 */
313367 std::optional<T> try_pop () /* noexcept(std::is_nothrow_constructible<T>::value) */ {
368+ if (m_stop_tok.stop_requested ()) {
369+ return std::optional<T> {};
370+ }
314371 lock_guard lk{m_mut};
315372 if (m_data_queue.empty ()) {
316373 return std::optional<T> {};
@@ -356,12 +413,13 @@ class wait_queue {
356413 }
357414
358415 /* *
359- * Query whether the @ close method has been called on the @c wait_queue.
416+ * Query whether a @ request_stop method (passed through a @c stop_token) has been called on
417+ * the @c wait_queue.
360418 *
361- * @return @c true if the @c wait_queue has been closed .
419+ * @return @c true if the @c stop_requested has been called .
362420 */
363- bool is_closed () const noexcept {
364- return m_closed ;
421+ bool stop_requested () const noexcept {
422+ return m_stop_tok. stop_requested () ;
365423 }
366424
367425 /* *
0 commit comments