Skip to content

Commit fdcb4e4

Browse files
Progress on new and improved wait queue, more test code is needed
1 parent 3872476 commit fdcb4e4

4 files changed

Lines changed: 284 additions & 276 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ cmake_minimum_required ( VERSION 3.14 FATAL_ERROR )
1212
project ( wait_queue
1313
LANGUAGES CXX
1414
DESCRIPTION "A multi producer / multi consumer thread safe wait queue"
15-
HOMEPAGE "https://githug.com/connectivecpp/wait-queue/" )
15+
HOMEPAGE_URL "https://githug.com/connectivecpp/wait-queue/" )
1616

1717
option ( WAIT_QUEUE_BUILD_TESTS "Build unit tests" OFF )
1818
option ( WAIT_QUEUE_BUILD_EXAMPLES "Build examples" OFF )
@@ -21,13 +21,13 @@ option ( WAIT_QUEUE_INSTALL "Install header only library" OFF )
2121
# add library targets
2222

2323
add_library ( wait_queue INTERFACE )
24-
add_library ( queue::wait_queue ALIAS wait_queue )
24+
add_library ( chops::wait_queue ALIAS wait_queue )
2525

2626
# configure library target
2727

2828
target_include_directories ( wait_queue INTERFACE
29-
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/queue/>
30-
$<INSTALL_INTERFACE:include/queue/> )
29+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/>
30+
$<INSTALL_INTERFACE:include/> )
3131
target_compile_features ( wait_queue INTERFACE cxx_std_20 )
3232

3333
# check to build unit tests
@@ -37,12 +37,12 @@ if ( ${WAIT_QUEUE_BUILD_TESTS} )
3737
endif ()
3838

3939
# check to build example code
40-
if ( ${WAIT_QUEUE_BUILD_EXAMPLES } )
40+
if ( ${WAIT_QUEUE_BUILD_EXAMPLES} )
4141
add_subdirectory ( example )
4242
endif ()
4343

4444
# check to install
45-
if ( ${WAIT_QUEUE_INSTALL } )
45+
if ( ${WAIT_QUEUE_INSTALL} )
4646
set ( CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt )
4747
include ( CPack )
4848
endif ()

‎include/queue/wait_queue.hpp‎

Lines changed: 116 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@
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
*
@@ -27,12 +33,12 @@
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
@@ -47,38 +53,40 @@
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
@@ -108,9 +116,9 @@
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 {
120128
template <typename T, typename Container = std::deque<T> >
121129
class wait_queue {
122130
private:
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

130139
public:
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

Comments
 (0)