Skip to content

Commit 3cc4c47

Browse files
Reorganizing documentation
1 parent ad3ff57 commit 3cc4c47

4 files changed

Lines changed: 145 additions & 141 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010

1111
## Overview
1212

13-
`wait_queue` is a multi-reader, multi-writer FIFO 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.
14-
15-
Additional documentation (which is also the main page of the generated Doxygen doc) can be found [here](doc/README.md).
13+
`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.
1614

1715
## Generated Documentation
1816

@@ -28,7 +26,7 @@ The `wait-queue` header file does not have any third-party dependencies. It uses
2826

2927
## Supported Compilers
3028

31-
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 incomplete as of May 2024.
29+
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`.
3230

3331
## Unit Test Dependencies
3432

@@ -39,7 +37,7 @@ The unit test uses two third-party libraries (each is a single header-only file)
3937
- Martin Moene's [ring-span-lite](https://github.com/martinmoene/ring-span-lite)
4038
- Justas Masiulis' [circular_buffer](https://github.com/JustasMasiulis/circular_buffer)
4139

42-
Specific versions (or branches) for the dependencies are in `test/CMakeLists.txt`.
40+
Specific version (or branch) specs for the dependencies are in `test/CMakeLists.txt`.
4341

4442
## Build and Run Unit Tests
4543

‎doc/Doxyfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ FILTER_SOURCE_PATTERNS =
10611061
# (index.html). This can be useful if you have a project on for instance GitHub
10621062
# and want to reuse the introduction page also for the doxygen output.
10631063

1064-
USE_MDFILE_AS_MAINPAGE = README.md
1064+
USE_MDFILE_AS_MAINPAGE =
10651065

10661066
#---------------------------------------------------------------------------
10671067
# Configuration options related to source browsing

‎doc/README.md‎

Lines changed: 0 additions & 24 deletions
This file was deleted.

‎include/queue/wait_queue.hpp‎

Lines changed: 141 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,144 @@
1-
/** @file
2-
*
3-
* @brief Multi-reader multi-writer wait queue class for transferring
4-
* data between threads.
5-
*
6-
* This class allows transferring data between threads with queue semantics
7-
* (push, pop), using C++ std library general facilities (mutex, condition
8-
* variable). An internal container is managed within this class.
9-
*
10-
* Multiple writer and reader threads can access this object, although when
11-
* a value is pushed, only one reader thread will be notified to consume a
12-
* value.
13-
*
14-
* One of the template parameters is the container type, allowing customization
15-
* for specific use cases (see below for additional details).
16-
*
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
24-
* are notified, and an empty value returned to those threads. Subsequent calls
25-
* to @c push will return a @c false value.
26-
*
27-
* Example usage, default container:
28-
*
29-
* @code
30-
* chops::wait_queue<int> wq;
31-
*
32-
* // inside writer thread, assume wq passed in by reference
33-
* wq.push(42);
34-
* ...
35-
* // all finished, time to shutdown
36-
* wq.request_stop();
37-
*
38-
* // inside reader thread, assume wq passed in by reference
39-
* auto rtn_val = wq.wait_and_pop(); // return type is std::optional<int>
40-
* if (!rtn_val) { // empty value, request_stop has been called
41-
* // time to exit reader thread
42-
* }
43-
* if (*rtn_val == 42) ...
44-
* @endcode
45-
*
46-
* Example usage with ring buffer (from Martin Moene):
47-
*
48-
* @code
49-
* const int sz = 20;
50-
* int buf[sz];
51-
* chops::wait_queue<int, nonstd::ring_span<int> > wq(buf+0, buf+sz);
52-
* // push and pop same as code with default container
53-
* @endcode
54-
*
55-
* The container type must support the following (depending on which
56-
* methods are called): default construction, construction from a
57-
* begin and end iterator, construction with an initial size,
58-
* @c push_back (preferably overloaded for both copy and move),
59-
* @c emplace_back (with a template parameter pack), @c front, @c pop_front,
60-
* @c empty, and @c size. The container must also have a @c size_type
61-
* defined.
62-
*
63-
* This class is based on code from the book Concurrency in Action by Anthony
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.
69-
*
70-
* @note A fixed size buffer can be used for the container, which eliminates
71-
* queue memory management happening during a @c push or @c pop. In particular,
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
74-
* Martin Moene. The constructor that takes an iterator range can be used for
75-
* a container view type, which means that the @c wait_queue owns and manages
76-
* a view rather than the underlying container buffer.
77-
*
78-
* @note The @c boost @c circular_buffer can be used for the container type. Memory is
79-
* allocated only once, at container construction time. This may be useful for
80-
* environments where construction can use dynamic memory but a @c push or @c pop
81-
* must not allocate or deallocate memory.
82-
*
83-
* @note If the container type is @c boost @c circular_buffer then the default
84-
* constructor for @c wait_queue cannot be used (since it would result in a container
85-
* with an empty capacity).
86-
*
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.
90-
*
91-
* @note Copy and move construction or assignment for the whole queue is
92-
* disallowed, since the use cases and underlying implications are not clear
93-
* for those operations. In particular, the exception implications for
94-
* assigning the internal data from one queue to another is messy, and the general
95-
* semantics of what it means is not clearly defined. If there is data in one
96-
* @c wait_queue that must be copied or moved to another, the @c apply method can
97-
* be used or individual @c push and @c pop methods called, even if not as efficient
98-
* as an internal copy or move.
99-
*
100-
* @note Very few methods are declared as @c noexcept since very few of the @c std::mutex,
101-
* @c std::condition_variable, and @c std::lock_guard methods are @c noexcept. It is
102-
* possible to declare the methods as conditionally @c noexcept (commented out code),
103-
* which would assume that no exceptions will escape from mutex or condition
104-
* variable objects (or if one does, @c std::terminate will be called).
105-
*
106-
* @authors Cliff Green, Anthony Williams
107-
*
108-
* Copyright (c) 2017-2024 by Cliff Green
109-
*
110-
* Distributed under the Boost Software License, Version 1.0.
111-
* (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
1+
/** @mainpage Wait Queue, a Multi-Writer / Multi-Reader (MPMC) Thread-Safe Queue
2+
*
3+
* This class allows transferring data between threads with queue semantics (push, pop),
4+
* using C++ std library general facilities (mutex, condition variable). An internal
5+
* container is managed within this class.
6+
*
7+
* Multiple writer and reader threads can access a @c wait_queue object simultaneously.
8+
* When a value is pushed on the queue by a writer thread, only one reader thread will be
9+
* notified to consume the value.
10+
*
11+
* One of the template parameters is the container type, allowing customization
12+
* for specific use cases (see below for additional details). The default container
13+
* type is @c std::deque.
14+
*
15+
* A graceful shutdown can be requested using the @c request_stop method (modeled on the
16+
* C++ 20 @c request_stop from @c std::stop_source). This allows waiting reader threads
17+
* to be notified for shutdown. Alternatively a @c std::stop_token can be passed in to
18+
* the @c wait_queue constructor, allowing shutdown from outside of the @c wait_queue
19+
* object.
20+
*
21+
* @c wait_queue uses C++ standard library concurrency facilities (e.g. @c std::mutex,
22+
* @c std::condition_variable_any) in its implementation. It is not a lock-free queue,
23+
* but it has been designed to be used in memory constrained environments or where
24+
* deterministic performance is needed.
25+
*
26+
* In particular, @c wait_queue:
27+
*
28+
* - Has been tested with Martin Moene's @c ring_span library for the internal container.
29+
* A @c ring_span is traditionally known as a "ring buffer" or "circular buffer". This
30+
* implies that the @c wait_queue can be used in environments where dynamic memory
31+
* management (heap) is not allowed or is problematic. In particular, no heap memory is
32+
* directly allocated within the @c wait_queue object.
33+
*
34+
* - Does not throw or catch exceptions anywhere in its code base. If a value being pushed
35+
* on to the queue throws an exception, it can be caught by the pushing code (or higher
36+
* up in the call chain). Exceptions may be thrown by C++ std library concurrency calls
37+
* (@c std::mutex locks, etc), as specified by the C++ standard, although this usually
38+
* indicates an application design issue or issues at the operating system level.
39+
*
40+
* - If the C++ std library concurrency calls become @c noexcept (instead of throwing an
41+
* exception), every @c wait_queue method will become @c noexcept or conditionally
42+
* @c noexcept (depending on the type of the data passed through the @c wait_queue).
43+
*
44+
* The only requirement on the type passed through a @c wait_queue is that it supports
45+
* either copy construction or move construction. In particular, a default constructor is
46+
* not required (this is enabled by using @c std::optional, which does not require a
47+
* default constructor).
48+
*
49+
* The implementation is adapted from the book Concurrency in Action, Practical
50+
* Multithreading, by Anthony Williams (2012 edition). The core logic in this library is
51+
* the same as provided by Anthony in his book, but C++ 20 features have been added,
52+
* the API is significantly changed and additional features added. The name of the utility
53+
* class template in Anthony's book is @c threadsafe_queue.
54+
*
55+
* Additional details:
56+
*
57+
* Each method is fully documented in the class documentation. In particular, function
58+
* arguments, pre-conditions, and return values are all documented.
59+
*
60+
* Once @c request_stop has been invoked (either through the @c wait_queue object or
61+
* from an external @c std::stop_source), subsequent pushes will not add any elements to
62+
* the queue and the @c push methods will return @c false.
63+
*
64+
* The @c push methods return a @c bool to denote whether a value was succesfully queued or
65+
* whether a shutdown was requested. The @c pop methods return a @c std::optional value.
66+
* For the @c wait_and_pop method, if the return value is not present it means a shutdown was
67+
* requested. For the @c try_pop method, if the return value is not present it means either
68+
* the queue was empty at that moment, or that a shutdown was requested.
69+
*
70+
* A @c std::stop_token can be passed in through the constructors, which allows
71+
* aa external @c std::stop_source to @c request_stop. Alternatively, an
72+
* internal @c stop_token will be used, allowing the @c wait_queue
73+
* @c request_stop method to be used to shutdown @c wait_queue processing.
74+
*
75+
* Once a @c request_stop is called (either externally or through the @c wait_queue
76+
* @c request_stop) all reader threads calling @c wait_and_pop are notified, and an empty
77+
* value returned to those threads. Subsequent calls to @c push will return a @c false value.
78+
*
79+
* Example usage, default container:
80+
*
81+
* @code
82+
* chops::wait_queue<int> wq;
83+
*
84+
* // inside writer thread, assume wq passed in by reference
85+
* wq.push(42);
86+
* ...
87+
* // all finished, time to shutdown
88+
* wq.request_stop();
89+
*
90+
* // inside reader thread, assume wq passed in by reference
91+
* auto rtn_val = wq.wait_and_pop(); // return type is std::optional<int>
92+
* if (!rtn_val) { // empty value, request_stop has been called
93+
* // time to exit reader thread
94+
* }
95+
* if (*rtn_val == 42) ...
96+
* @endcode
97+
*
98+
* Example usage with ring buffer (from Martin Moene):
99+
*
100+
* @code
101+
* const int sz = 20;
102+
* int buf[sz];
103+
* chops::wait_queue<int, nonstd::ring_span<int> > wq(buf+0, buf+sz);
104+
* // push and pop same as code with default container
105+
* @endcode
106+
*
107+
* The container type must support the following (depending on which
108+
* methods are called): default construction, construction from a
109+
* begin and end iterator, construction with an initial size,
110+
* @c push_back (preferably overloaded for both copy and move),
111+
* @c emplace_back (with a template parameter pack), @c front, @c pop_front,
112+
* @c empty, and @c size. The container must also have a @c size_type
113+
* defined.
114+
*
115+
* Iterators on a @c wait_queue are not supported, due to obvious difficulties
116+
* with maintaining consistency and integrity. The @c apply method can be used to
117+
* access the internal data in a threadsafe manner.
118+
*
119+
* Copy and move construction or assignment for the whole queue is
120+
* disallowed, since the use cases and underlying implications are not clear
121+
* for those operations. In particular, the exception implications for
122+
* assigning the internal data from one queue to another is messy, and the general
123+
* semantics of what it means is not clearly defined. If there is data in one
124+
* @c wait_queue that must be copied or moved to another, the @c apply method can
125+
* be used or individual @c push and @c pop methods called, even if not as efficient
126+
* as an internal copy or move.
127+
*
128+
* @note The @c boost @c circular_buffer can be used for the container type. Memory is
129+
* allocated only once, at container construction time. This may be useful for
130+
* environments where construction can use dynamic memory but a @c push or @c pop
131+
* must not allocate or deallocate memory. If the container type is @c boost
132+
* @c circular_buffer then the default constructor for @c wait_queue cannot be used
133+
* (since it would result in a container with an empty capacity).
134+
*
135+
*
136+
* @authors Cliff Green, Anthony Williams
137+
*
138+
* Copyright (c) 2017-2024 by Cliff Green
139+
*
140+
* Distributed under the Boost Software License, Version 1.0.
141+
* (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
112142
*
113143
*/
114144

0 commit comments

Comments
 (0)