asynchrony 2.3.1
Add asynchrony to your C++ applications using standard C++20
Loading...
Searching...
No Matches
simple_pool.hpp
1/*
2 basic-pool : Add asynchrony to your apps
3
4 BSD 3-Clause License
5
6 Copyright (c) 2021, Siddiq Software LLC
7 All rights reserved.
8
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
11
12 1. Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
14
15 2. Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
18
19 3. Neither the name of the copyright holder nor the names of its
20 contributors may be used to endorse or promote products derived from
21 this software without specific prior written permission.
22
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35#pragma once
36#ifndef SIMPLE_POOL_HPP
37#define SIMPLE_POOL_HPP
38
39#include "simple_worker.hpp"
40#include <optional>
41#include <latch>
42#include <exception>
43#include <atomic>
44#include <utility>
45
46#include "siddiqsoft/WaitableQueue.hpp"
47#include "siddiqsoft/RunOnEnd.hpp"
48
49namespace siddiqsoft
50{
89 template <typename T, uint16_t N = 0>
90 requires std::is_move_constructible_v<T>
92 {
94 static constexpr std::chrono::milliseconds DEFAULT_WAIT_FOR_NEXT_ITEM_MS {1500};
95
98
101
104
107
108
121 {
122 // Compared to skipping the following code, we save at least about 100ms
123 // of idle time waiting for the threads to be signalled by default.
124 // Request all threads to stop first
125 for (auto& t : workers) {
126 t.request_stop();
127 }
128
129 // Then wake them up and join
130 for (auto& t : workers) {
131 signal.release();
132 if (t.joinable()) t.join();
133 }
134 }
135
152 simple_pool(std::function<void(T&&)> c)
153 : callback(std::move(c))
154 {
155 // *CRITICAL*
156 // This is step is *critical* otherwise we will end up moving threads as we add elements to the vector.
157 workers.reserve((N > 0) ? N : std::thread::hardware_concurrency());
158
159 // Create as many threads as reported by the system..
160 for (unsigned i = 0; i < ((N > 0) ? N : std::thread::hardware_concurrency()); i++) {
161 // Add the thread with the main driver
162 workers.emplace_back([&](std::stop_token st) {
163 // The driver runs forever until signalled to stop
164 // Tries to get next item ready in the queue (for max 1s cycle)
165 // If we have an item, invoke the callback with the item
166 while (!st.stop_requested()) {
167 try {
168 // The getNextItem performs the wait on the signal and if it expires, returns empty.
169 // If there is an item, it will get that item (minimizing move) and performs the pop
170 // and returns the item so we can invoke the callback outside the lock.
171 if (auto item = getNextItem(); item.has_value() && !st.stop_requested() && callback) {
172 // Delegate to the callback outside the lock
173 callback(std::move(*item));
174 }
175 }
176 catch (const std::exception& ex) {
177 // We swallow exceptions from the callback to avoid thread termination and log it if needed.
178 std::cerr << std::format("Ignoring Exception in simple_worker callback: {}", ex.what());
179 }
180 } // while ..continue until we're asked to stop
181 });
182 }
183 }
184
202 void queue(T&& item)
203 {
204 // With this interface, we can peform a perfect forward of the r-value from the caller into the
205 // items internal container without the complexity of lambda capture forwards.
206 items.emplace(std::forward<T>(item));
207
208 // Use atomic fetch_add with release semantics to ensure thread-safe updates
209 queueCounter.fetch_add(1, std::memory_order_release);
210 signal.release();
211 }
212
213#if defined(NLOHMANN_JSON_VERSION_MAJOR)
231 auto toJson() const -> nlohmann::json
232 {
233 const auto sz = items.size();
234 return nlohmann::json {{"_typver", "siddiqsoft.asynchrony-lib.simple_pool/0.10"},
235 {"workersSize", workers.size()},
236 {"dequeSize", sz},
237 {"queueCounter", queueCounter.load(std::memory_order_acquire)},
238 {"waitInterval", DEFAULT_WAIT_FOR_NEXT_ITEM_MS.count()}};
239 }
240#endif
241
242#ifdef _DEBUG
243 public:
245 std::atomic_uint64_t queueCounter {0};
246#else
247 private:
249 std::atomic_uint64_t queueCounter {0};
250#endif
251
252 private:
254 std::vector<std::jthread> workers {};
255
257 std::function<void(T&&)> callback;
258
260 std::counting_semaphore<> signal {0};
261
263 siddiqsoft::WaitableQueue<T> items {};
264
280 std::optional<T> getNextItem(const std::chrono::milliseconds& delta = DEFAULT_WAIT_FOR_NEXT_ITEM_MS)
281 {
282 return items.tryWaitItem(delta);
283 }
284 };
285
286#if defined(NLOHMANN_JSON_VERSION_MAJOR)
297 template <typename T, uint16_t N = 0>
298 static auto to_json(nlohmann::json& dest, const siddiqsoft::simple_pool<T, N>& src) -> void const
299 {
300 dest = src.toJson();
301 }
302#endif
303
304} // namespace siddiqsoft
305#endif // !SIMPLE_POOL_HPP
simple_pool & operator=(simple_pool &)=delete
Copy assignment operator (deleted - pools are not copyable).
void queue(T &&item)
Queue a work item for processing.
static constexpr std::chrono::milliseconds DEFAULT_WAIT_FOR_NEXT_ITEM_MS
Default wait interval for threads waiting on the semaphore.
simple_pool(std::function< void(T &&)> c)
Constructs a thread pool with N worker threads.
simple_pool(simple_pool &)=delete
Copy constructor (deleted - pools are not copyable).
simple_pool(simple_pool &&)=delete
Move constructor (deleted - pools are not movable).
simple_pool & operator=(simple_pool &&)=delete
Move assignment operator (deleted - pools are not movable).
~simple_pool()
Destructor - gracefully shuts down all worker threads.