opynsim
Unofficial C++ Documentation
Loading...
Searching...
No Matches
spsc.h
Go to the documentation of this file.
1#pragma once
2
5
6#include <atomic>
7#include <condition_variable>
8#include <cstddef>
9#include <list>
10#include <memory>
11#include <mutex>
12#include <optional>
13#include <utility>
14
15// extremely basic support for a single-producer single-consumer (sp-sc) queue
16namespace osc
17{
18 template<typename T>
19 class SpscSender;
20
21 template<typename T>
22 class SpscReceiver;
23
24 namespace detail
25 {
26 // internal implementation class
27 template<typename T>
29 private:
30 // queue mutex
31 std::mutex mutex_;
32
33 // queue != empty condition variable for worker
34 std::condition_variable condition_variable_;
35
36 // the queue
37 std::list<T> message_queue_;
38
39 // how many `SpscSender` classes use this SpscImplementation (should be 1/0)
40 std::atomic<size_t> num_senders_ = 0;
41
42 // how many `SpscReceiver` classes use this impl (should be 1/0)
43 std::atomic<size_t> num_receivers_ = 0;
44
45 template<typename U>
46 friend std::pair<SpscSender<U>, SpscReceiver<U>> make_spsc_channel();
47 friend class SpscSender<T>;
48 friend class SpscReceiver<T>;
49 };
50 }
51
52 // a class the client can send information through
53 template<typename T>
55 std::shared_ptr<detail::SpscImplementation<T>> impl_;
56
57 template<typename U>
58 friend std::pair<SpscSender<U>, SpscReceiver<U>> make_spsc_channel();
59
60 explicit SpscSender(std::shared_ptr<detail::SpscImplementation<T>> impl) :
61 impl_{std::move(impl)}
62 {
63 ++impl_->num_senders_;
64 }
65 public:
66 SpscSender(const SpscSender&) = delete;
70
72 {
73 if (impl_) {
74 --impl_->num_senders_;
75 impl_->condition_variable_.notify_all(); // so receivers can know the hangup happened
76 }
77 }
78
79 // asynchronously (non-blocking) send data
80 void send(T v) {
81 {
82 std::lock_guard l{impl_->mutex_};
83 impl_->message_queue_.push_front(std::move(v));
84 }
85 impl_->condition_variable_.notify_one();
86 }
87
89 {
90 return impl_->num_receivers_ == 0;
91 }
92 };
93
94 // a class the client can receive data from
95 template<typename T>
97 std::shared_ptr<detail::SpscImplementation<T>> impl_;
98
99 template<typename U>
100 friend std::pair<SpscSender<U>, SpscReceiver<U>> make_spsc_channel();
101
102 explicit SpscReceiver(std::shared_ptr<detail::SpscImplementation<T>> impl) :
103 impl_{std::move(impl)}
104 {
105 ++impl_->num_receivers_;
106 }
107 public:
108 SpscReceiver(SpscReceiver const&) = delete;
113 {
114 if (impl_) {
115 --impl_->num_receivers_;
116 }
117 }
118
119 // non-blocking: empty if nothing is sent, or the sender has hung up
120 std::optional<T> try_receive()
121 {
122 std::lock_guard l{impl_->mutex_};
123
124 if (impl_->message_queue_.empty()) {
125 return std::nullopt;
126 }
127
128 std::optional<T> rv{std::move(impl_->message_queue_.back())};
129 impl_->message_queue_.pop_back();
130 return rv;
131 }
132
133 // blocking: only empty if the sender hung up
134 std::optional<T> receive()
135 {
136 std::unique_lock l{impl_->mutex_};
137
138 // easy case: queue is not empty
139 if (not impl_->message_queue_.empty())
140 {
141 std::optional<T> rv{std::move(impl_->message_queue_.back())};
142 impl_->message_queue_.pop_back();
143 return rv;
144 }
145
146 // harder case: sleep until the queue is not empty, *or* until
147 // the sender hangs up
148 impl_->condition_variable_.wait(l, [&]()
149 {
150 return not impl_->message_queue_.empty() or impl_->num_senders_ == 0;
151 });
152
153 // the condition variable woke up (non-spuriously), either:
154 //
155 // - there's something in the queue (return it)
156 // - the sender hung up (return `std::nullopt`)
157
158 if (not impl_->message_queue_.empty()) {
159 std::optional<T> rv{std::move(impl_->message_queue_.back())};
160 impl_->message_queue_.pop_back();
161 return rv;
162 }
163 else {
164 return std::nullopt;
165 }
166 }
167
169 {
170 return impl_->num_senders_ == 0;
171 }
172 };
173
174 // create a new thread-safe spsc channel (sender + receiver)
175 template<typename T>
176 std::pair<SpscSender<T>, SpscReceiver<T>> make_spsc_channel()
177 {
178 auto impl = std::make_shared<detail::SpscImplementation<T>>();
179 return {SpscSender<T>{impl}, SpscReceiver<T>{impl}};
180 }
181
182 // SPSC worker: single-producer single-consumer worker abstraction
183 //
184 // encapsulates a worker background thread with thread-safe communication
185 // channels
186 template<typename Input, typename Output, typename Func>
188
189 // worker (background thread)
190 cpp20::jthread worker_thread_;
191
192 // sending end of the channel: sends inputs to background thread
193 SpscSender<Input> sender_;
194
195 // receiving end of the channel: receives outputs from background thread
196 SpscReceiver<Output> receiver_;
197
198 // MAIN function for an SPSC worker thread
199 static int main(
204 {
205 // continuously try to receive input messages and
206 // respond to them one-by-one
207 while (not sender.is_receiver_hung_up()) {
208 std::optional<Input> message = receiver.receive();
209
210 if (not message) {
211 return 0; // sender hung up
212 }
213
214 sender.send(message_processor(*message));
215 }
216
217 return 0; // receiver hung up
218 }
219
224
225 worker_thread_{std::move(worker)},
226 sender_{std::move(sender)},
227 receiver_{std::move(receiver)}
228 {}
229
230 public:
231
232 // create a new worker
234 {
237 cpp20::jthread worker{SpscWorker::main, std::move(request_receiver), std::move(response_sender), std::move(message_processor)};
238 return SpscWorker{std::move(worker), std::move(request_sender), std::move(response_receiver)};
239 }
240
242 {
243 sender_.send(std::move(req));
244 }
245
246 std::optional<Output> poll()
247 {
248 return receiver_.try_receive();
249 }
250 };
251}
Definition spsc.h:96
SpscReceiver(SpscReceiver const &)=delete
SpscReceiver(SpscReceiver &&) noexcept=default
std::optional< T > receive()
Definition spsc.h:134
std::optional< T > try_receive()
Definition spsc.h:120
friend std::pair< SpscSender< U >, SpscReceiver< U > > make_spsc_channel()
Definition spsc.h:176
bool is_sender_hung_up()
Definition spsc.h:168
Definition spsc.h:54
SpscSender(const SpscSender &)=delete
SpscSender(SpscSender &&) noexcept=default
void send(T v)
Definition spsc.h:80
bool is_receiver_hung_up()
Definition spsc.h:88
friend std::pair< SpscSender< U >, SpscReceiver< U > > make_spsc_channel()
Definition spsc.h:176
Definition spsc.h:187
static SpscWorker create(Func message_processor)
Definition spsc.h:233
std::optional< Output > poll()
Definition spsc.h:246
void send(Input req)
Definition spsc.h:241
Definition thread.h:21
Definition stop_token.h:19
friend std::pair< SpscSender< U >, SpscReceiver< U > > make_spsc_channel()
Definition spsc.h:176
Definition custom_decoration_generator.h:5
std::pair< SpscSender< T >, SpscReceiver< T > > make_spsc_channel()
Definition spsc.h:176
constexpr U to(T &&value)
Definition conversion.h:56