8000 Merge pull request #18 from connectivecpp/develop · connectivecpp/wait-queue@3d1aaf8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3d1aaf8

Browse files
< 10000 span class="ws-pre-wrap f5 wb-break-word text-mono prc-Text-Text-0ima0">
Merge pull request #18 from connectivecpp/develop
Merge develop to main
2 parents 376b236 + 6fdba5f commit 3d1aaf8

File tree

2 files changed

+110
-52
lines changed

2 files changed

+110
-52
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Wait Queue, a Header-Only C++ 20 MPMC Thread-Safe Queue
1+
# Wait Queue, a Header-Only C++ 20 MPMC Thread-Safe Queue With Shutdown Semantics
22

33
#### Unit Test and Documentation Generation Workflow Status
44

@@ -16,8 +16,12 @@
1616

1717
`wait_queue` is a multi-reader, multi-writer FIFO thread-safe wait queue (often called MPMC for multiple producer / multiple consumer) for transferring data between threads. It is templatized on the type of data passed through the queue as well as the queue container type. Data is passed with value semantics, either by copying or by moving (as opposed to a queue that transfers data by pointer or reference). The wait queue has both wait and no-wait pop semantics. A fixed size container (e.g. a `ring_span`) can be used, eliminating any and all dynamic memory management (useful in embedded or deterministic environments). Similarly, a circular buffer that only allocates on construction can be used, which eliminates dynamic memory management when pushing or popping values on or off the queue.
1818

19+
Shutdown semantics are available through `std::stop_token` facilities. A `std::stop_token` can be passed in through the constructors, allowing shutdown to be requested externally to the `wait_queue`, or shutdown can be requested through the `wait_queue request_stop` method.
20+
1921
Thanks go to [Louis Langholtz](https://github.com/louis-langholtz) for adding DBC (Design by Contract) asserts and comments.
2022

23+
Concepts and various type constraints have been added. Enhancements are always appreciated.
24+
2125
## Generated Documentation
2226

2327
The generated Doxygen documentation for `wait_queue` is [here](https://connectivecpp.github.io/wait-queue/).
@@ -28,11 +32,11 @@ The `wait_queue` header file does not have any third-party dependencies. It uses
2832

2933
## C++ Standard
3034

31-
`wait_queue` uses C++ 20 features, including `std::stop_token`, `std::stop_source`, `std::condition_variable_any`, `std::scoped_lock`, and `concepts` / `requires`.
35+
`wait_queue` uses C++ 20 features, including `std::stop_token`, `std::stop_source`, `std::condition_variable_any`, `std::scoped_lock`, `concepts`, and `requires` clauses.
3236

3337
## Supported Compilers
3438

35-
Continuous integration workflows build and unit test on g++ (through Ubuntu) and MSVC (through Windows). Note that clang support for C++ 20 `std::jthread` and `std::stop_token` is still experimental (and possibly incomplete) as of May 2024, so has not (yet) been tested with `wait_queue`.
39+
Continuous integration workflows build and unit test on g++ (through Ubuntu) and MSVC (through Windows). Note that clang support for C++ 20 `std::jthread` and `std::stop_token` is still experimental (and possibly incomplete) as of Sep 2024, so has not (yet) been tested with `wait_queue`.
3640

3741
## Unit Test Dependencies
3842

include/queue/wait_queue.hpp

Lines changed: 103 additions & 49 deletions
424
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,11 @@
112112
*
113113
* The container type must support the following (depending on which
114114
* methods are called): default construction, construction with an initial size,
115-
* @c push_back (preferably overloaded for both copy and move),
115+
* @c push_back (preferably overloaded for both copy and move semantics),
116116
* @c emplace_back (with a template parameter pack), @c front, @c pop_front,
117117
* @c empty, and @c size. The container must also have a @c size_type
118-
* defined.
118+
* defined. Type constraints and concepts are defined for the various
119+
* type requirements.
119120
*
120121
* Iterators on a @c wait_queue are not supported, due to obvious difficulties
121122
* with maintaining consistency and integrity. The @c apply method can be used to
@@ -157,12 +158,43 @@
157158
#include <condition_variable>
158159
#include <stop_token> // std::stop_source, std::stop_token
159160
#include <optional>
160-
#include <utility> // std::move, std::move_if_noexcept
161-
#include <type_traits> // for noexcept specs
161+
#include <utility> // std::move, std::move_if_noexcept, std::forward
162+
#include <type_traits> // for requires clauses and noexcept specs
163+
// #include <concepts>
162164

163165
namespace chops {
164166

167+
// requirements for wait_queue container
168+
169+
template <typename Ctr, typename T>
170+
concept supports_push_back = requires (Ctr ctr, T val) {
171+
ctr.push_back(val);
172+
};
173+
174+
template <typename Ctr, typename ... Args>
175+
concept supports_emplace_back = requires (Ctr ctr, Args&& ... args) {
176+
ctr.emplace_back(args ...);
177+
};
178+
179+
template <typename Ctr>
180+
concept supports_empty = requires (Ctr ctr) {
181+
ctr.empty();
182+
};
183+
184+
template <typename Ctr>
185+
concept supports_pop_front = requires (Ctr ctr) {
186+
ctr.pop_front();
187+
};
188+
189+
template <typename Ctr>
190+
concept supports_size = requires (Ctr ctr) {
191+
ctr.size();
192+
};
193+
194+
// declaration for wait_queue
195+
165196
template <typename T, typename Container = std::deque<T> >
197+
requires std::is_copy_constructible_v<T> || std::is_move_constructible_v<T>
166198
class wait_queue {
167199
private:
168200
mutable std::mutex m_mut;
@@ -195,8 +227,10 @@ class wait_queue {
195227
* @post @c stop_requested returns @c false.
196228
*/
197229
wait_queue()
198-
// noexcept(std::is_nothrow_constructible<Container>::value)
199-
: m_stop_src(std::stop_source{}), m_stop_tok((*m_stop_src).get_token()) {
230+
requires std::is_default_constructible_v<Container>
231+
// noexcept(std::is_nothrow_constructible_v<Container>)
232+
: m_stop_src(std::stop_source{}), m_stop_tok((*m_stop_src).get_token())
233+
{
200234
assert(empty());
201235
assert(size() == size_type(0));
202236
assert(!stop_requested());
@@ -212,8 +246,10 @@ class wait_queue {
212246
* @post @c size returns 0.
213247
*/
214248
wait_queue(std::stop_token stop_tok)
215-
// noexcept(std::is_nothrow_constructible<Container>::value)
216-
: m_stop_tok(stop_tok) {
249+
requires std::is_default_constructible_v<Container>
250+
// noexcept(std::is_nothrow_constructible_v<Container, std::stop_token>)
251+
: m_stop_tok(stop_tok)
252+
{
217253
assert(empty());
218254
assert(size() == size_type(0));
219255
}
@@ -236,15 +272,16 @@ class wait_queue {
236272
* @param container Container object to be moved from (or copied from if not
237273
* movable).
238274
*
239-
* @post @c empty returns @c true if @c beg equals @c end otherwise returns @c false.
240-
* @post @c size returns the distance between @c beg and @c end parameters.
275+
* @post @c empty and @c size match moved (or copied) in container.
241276
*/
242277
wait_queue(Container&& container)
243-
// noexcept(std::is_ something movable or maybe copyable )
278+
requires std::is_move_constructible_v<Container> ||
279+
std::is_copy_constructible_v<Container>
280+
// noexcept(std::is_nnthrow_constructible_v<Container, Container&&>)
244281
: m_stop_src(std::stop_source{}), m_stop_tok((*m_stop_src).get_token()),
245-
m_data_queue(std::move(container)) {
246-
// assert(empty() == (beg == end));
247-
// assert((size() == size_type(0)) == (beg == end)); // std::distance constrains beg, end.
282+
m_data_queue(std::move(container))
283+
{
284+
// not easily assertible until contracts added to C++
248285
}
249286

250287
/**
@@ -257,14 +294,14 @@ class wait_queue {
257294
* @param container Container object to be moved from (or copied from if not
258295
* movable).
259296
*
260-
* @post @c empty returns @c true if @c beg equals @c end otherwise returns @c false.
261-
* @post @c size returns the distance between @c beg and @c end parameters.
297+
* @post @c empty and @c size match moved (or copied) in container.
262298
*/
263299
wait_queue(std::stop_token stop_tok, Container&& container)
264-
// noexcept(std::is_nothrow_constructible<Container, Iter, Iter>::value)
265-
: m_stop_tok(stop_tok), m_data_queue(std::move(container)) {
266-
// assert(empty() == (beg == end));
267-
// assert((size() == size_type(0)) == (beg == end)); // std::distance constrains beg, end.
300+
requires std::is_move_constructible_v<Container> ||
301+
std::is_copy_constructible_v<Container>
302+
// noexcept(std::is_nothrow_constructible_v<Container, std::stop_token, Container&&>)
303+
: m_stop_tok(stop_tok), m_data_queue(std::move(container))
304+
{
268305
}
269306

270307
/**
@@ -290,9 +327,11 @@ class wait_queue {
290327
* @post @c size returns 0 or @c sz depending on container used.
291328
*/
292329
wait_queue(size_type sz)
293-
// noexcept(std::is_nothrow_constructible<Container, size_type>::value)
330+
requires std::is_constructible_v<Container, size_type>
331+
// noexcept(std::is_nothrow_constructible_v<Container, size_type>)
294332
: m_stop_src(std::stop_source{}), m_stop_tok((*m_stop_src).get_token()),
295-
m_data_queue(sz) {
333+
m_data_queue(sz)
334+
{
296335
assert((sz != size_type(0)) || empty());
297336
assert((size() == size_type(0)) || (size() == sz));
298337
}
@@ -310,8 +349,10 @@ class wait_queue {
310349
* @post @c size returns 0 or @c sz depending on container used.
311350
*/
312351
wait_queue(std::stop_token stop_tok, size_type sz)
313-
// noexcept(std::is_nothrow_constructible<Container, size_type>::value)
314-
: m_stop_tok((*m_stop_src).get_token()), m_data_queue(sz) {
352+
requires std::is_constructible_v<Container, std::stop_token, size_type>
353+
// noexcept(std::is_nothrow_constructible_v<Container, std::stop_token, size_type>)
354+
: m_stop_tok((*m_stop_src).get_token()), m_data_queue(sz)
355+
{
315356
assert((sz != size_type(0)) || empty());
316357
assert((size() == size_type(0)) || (size() == sz));
317358
}
@@ -341,13 +382,12 @@ class wait_queue {
341382
* external @c std::stop_token was passed in.
342383
*/
343384
auto request_stop() noexcept
344-
-> bool {
345-
385+
-> bool
386+
{
346387
if (m_stop_src) {
347388
return (*m_stop_src).request_stop();
348389
}
349390
return false;
350-
351391
}
352392

353393
/**
@@ -364,9 +404,11 @@ class wait_queue {
364404
* @post If @c true is returned and @c empty is @c false, one of any threads waiting for a
365405
* value will be unblocked.
366406
*/
367-
auto push(const T& val) /* noexcept(std::is_nothrow_copy_constructible<T>::value) */
368-
-> bool {
407+
auto push(const T& val) /* noexcept(std::is_nothrow_copy_constructible_v<T>) */
408+
-> bool
409+
requires supports_push_back<Container, T>
369410

411+
{
370412
if (m_stop_tok.stop_requested()) {
371413
return false;
372414
}
@@ -386,9 +428,11 @@ class wait_queue {
386428
* @post If @c true is returned and @c empty is @c false, one of any threads waiting for a
387429
* value will be unblocked.
388430
*/
389-
auto push(T&& val) /* noexcept(std::is_nothrow_move_constructible<T>::value) */
390-
-> bool {
431+
auto push(T&& val) /* noexcept(std::is_nothrow_move_constructible_v<T>) */
432+
-> bool
433+
requires supports_push_back<Container, T>
391434

435+
{
392436
if (m_stop_tok.stop_requested()) {
393437
return false;
394438
}
@@ -416,17 +460,18 @@ class wait_queue {
416460
* value will be unblocked.
417461
*/
418462
template <typename ... Args>
419-
auto emplace_push(Args &&... args) /* noexcept(std::is_nothrow_constructible<T, Args...>::value)*/
420-
-> bool {
421-
463+
auto emplace_push(Args &&... args) /* noexcept(std::is_nothrow_constructible_v<T, Args...>)*/
464+
-> bool
465+
requires supports_emplace_back<Container, Args...>
466+
467+
{
422468
if (m_stop_tok.stop_requested()) {
423469
return false;
470
}
425471
lock_guard lk{m_mut};
426472
m_data_queue.emplace_back(std::forward<Args>(args)...);
427473
m_data_cond.notify_one();
428474
return true;
429-
430475
}
431476

432477
/**
@@ -443,9 +488,11 @@ class wait_queue {
443488
* @post If a non empty value is returned, until a push function is called, @c size is one
444489
* less than before this function was called.
445490
*/
446-
auto wait_and_pop() /* noexcept(std::is_nothrow_constructible<T>::value) */
447-
-> std::optional<T> {
491+
[[nodiscard]] auto wait_and_pop() /* noexcept(std::is_nothrow_constructible_v<T>) */
492+
-> std::optional<T>
493+
requires supports_empty<Container> && supports_pop_front<Container>
448494

495+
{
449496
std::unique_lock<std::mutex> lk{m_mut};
450497
if (!m_data_cond.wait ( lk, m_stop_tok, [this] { return !m_data_queue.empty(); } )) {
451498
return std::optional<T> {}; // queue was request to stop, no data available
@@ -472,9 +519,10 @@ class wait_queue {
472519
* @post If a non empty value is returned, until a push function is called, @c size is one
473520
* less than before this function was called.
474521
*/
475-
auto try_pop() /* noexcept(std::is_nothrow_constructible<T>::value) */
476-
-> std::optional<T> {
477-
522+
[[nodiscard]] auto try_pop() /* noexcept(std::is_nothrow_constructible_v<T>) */
523+
-> std::optional<T>
524+
requires supports_empty<Container> && supports_pop_front<Container>
525+
{
478526
if (m_stop_tok.stop_requested()) {
479527
return std::optional<T> {};
480528
}
@@ -520,9 +568,11 @@ class wait_queue {
520568
* same @c wait_queue since it results in recursive mutex locks.
521569
*/
522570
template <typename F>
523-
auto apply(F&& func) const /* noexcept(std::is_nothrow_invocable<F&&, const T&>::value) */
524-
-> void {
571+
auto apply(F&& 10000 amp; func) const /* noexcept(std::is_nothrow_invocable_v<F&&, const T&>) */
572+
-> void
573+
requires std::is_invocable_v<F, T>
525574

575+
{
526576
lock_guard lk{m_mut};
527577
for (const T& elem : m_data_queue) {
528578
func(elem);
@@ -536,21 +586,23 @@ class wait_queue {
536586
*
537587
* @return @c true if the @c stop_requested has been called.
538588
*/
539-
auto stop_requested() const noexcept
540-
-> bool {
589+
[[nodiscard]] auto stop_requested() const noexcept
590+
-> bool
541591

592+
{
542593
return m_stop_tok.stop_requested();
543-
544594
}
545595

546596
/**
547597
* Query whether the @c wait_queue is empty or not.
548598
*
549599
* @return @c true if the @c wait_queue is empty.
550600
*/
551-
auto empty() const /* noexcept */
552-
-> bool {
601+
[[nodiscard]] auto empty() const /* noexcept */
602+
-> bool
603+
requires supports_empty<Container>
553604

605+
{
554606
lock_guard lk{m_mut};
555607
return m_data_queue.empty();
556608

@@ -561,9 +613,11 @@ class wait_queue {
561613
*
562614
* @return Number of elements in the @c wait_queue.
563615
*/
564-
auto size() const /* noexcept */
565-
-> size_type {
616+
[[nodiscard]] auto size() const /* noexcept */
617+
-> size_type
618+
requires supports_size<Container>
566619

620+
{
567621
lock_guard lk{m_mut};
568622
return m_data_queue.size();
569623

0 commit comments

Comments
 (0)
0