Bertini 2 d882924 (2026-10-06 19:56 UTC)
Numerical algebraic geometry library (C++ core)
Loading...
Searching...
No Matches
thread_pool.hpp
Go to the documentation of this file.
1//This file is part of Bertini 2.
2//
3//bertini2/parallel/thread_pool.hpp is free software: you can redistribute it and/or modify
4//it under the terms of the GNU General Public License as published by
5//the Free Software Foundation, either version 3 of the License, or
6//(at your option) any later version.
7//
8//bertini2/parallel/thread_pool.hpp is distributed in the hope that it will be useful,
9//but WITHOUT ANY WARRANTY; without even the implied warranty of
10//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11//GNU General Public License for more details.
12//
13//You should have received a copy of the GNU General Public License
14//along with bertini2/parallel/thread_pool.hpp. If not, see <http://www.gnu.org/licenses/>.
15//
16// Copyright(C) Bertini2 Development Team
17
53
54#pragma once
55
56// NOTE: this header is intentionally NOT gated behind BERTINI2_HAVE_MPI. The thread pool is
57// pure <thread>/<mutex>/<condition_variable> with no MPI dependency; it backs both the
58// MPI+threads hybrid worker AND the MPI-less shared-memory threaded solve. See ADR / the
59// "MPI-less threading" plan: threading must be available in builds (and wheels) compiled
60// without any MPI installation.
61
62#if defined(__linux__)
63#include <sched.h> // sched_getaffinity, CPU_COUNT (AvailableCpuCount)
64#endif
65
66#include <condition_variable>
67#include <cstdlib> // std::getenv, std::atoi
68#include <deque>
69#include <functional>
70#include <mutex>
71#include <optional>
72#include <thread>
73#include <type_traits>
74#include <variant>
75#include <vector>
76
77namespace bertini {
78namespace parallel {
79
80
89inline unsigned AvailableCpuCount()
90{
91#if defined(__linux__)
92 cpu_set_t mask;
93 CPU_ZERO(&mask);
94 if (sched_getaffinity(0, sizeof(mask), &mask) == 0)
95 {
96 const int n = CPU_COUNT(&mask);
97 if (n >= 1)
98 return static_cast<unsigned>(n);
99 }
100#endif
101 const unsigned hw = std::thread::hardware_concurrency();
102 return hw >= 1 ? hw : 1u;
103}
104
105
120// b2 reads its own variable, never OMP_NUM_THREADS: numpy's OpenBLAS takes its thread count from
121// that one, so a shared name would tie the two libraries' thread counts together (ADR-0070).
122inline unsigned EffectiveThreadCount(unsigned configured = 0)
123{
124 if (const char* env = std::getenv("BERTINI_NUM_THREADS"))
125 {
126 int n = std::atoi(env);
127 if (n >= 1)
128 return static_cast<unsigned>(n);
129 }
130
131 if (configured >= 1)
132 return configured;
133
134 return AvailableCpuCount();
135}
136
137
139template<typename T>
141{
142 std::deque<T> queue_;
143 std::mutex mutex_;
144 std::condition_variable cv_;
145
146public:
148 void push(T item)
149 {
150 {
151 std::lock_guard<std::mutex> lock(mutex_);
152 queue_.push_back(std::move(item));
153 }
154 cv_.notify_one();
155 }
156
158 T pop()
159 {
160 std::unique_lock<std::mutex> lock(mutex_);
161 cv_.wait(lock, [this]{ return !queue_.empty(); });
162 T item = std::move(queue_.front());
163 queue_.pop_front();
164 return item;
165 }
166
168 std::optional<T> try_pop()
169 {
170 std::lock_guard<std::mutex> lock(mutex_);
171 if (queue_.empty())
172 return std::nullopt;
173 T item = std::move(queue_.front());
174 queue_.pop_front();
175 return item;
176 }
177};
178
179
181
182
196template<typename TaskT, typename ResultT, typename StateFactory, typename TrackFn>
198{
199 using StateT = std::invoke_result_t<StateFactory>;
200 using WorkItem = std::variant<TaskT, PoolShutdownSentinel>;
201
202 ThreadSafeQueue<WorkItem> task_queue_;
203 ThreadSafeQueue<ResultT> result_queue_;
204 std::vector<std::thread> threads_;
205 int n_threads_;
206
207public:
212 WorkerThreadPool(int n_threads, StateFactory state_factory, TrackFn track_fn)
213 : n_threads_(n_threads)
214 {
215 threads_.reserve(static_cast<size_t>(n_threads));
216 for (int i = 0; i < n_threads; ++i)
217 {
218 threads_.emplace_back([this, state_factory, track_fn]() mutable
219 {
220 // Build thread-local state once (System copy + Tracker copy).
221 StateT state = state_factory();
222
223 while (true)
224 {
225 WorkItem item = task_queue_.pop();
226
227 if (std::holds_alternative<PoolShutdownSentinel>(item))
228 break;
229
230 TaskT const& task = std::get<TaskT>(item);
231 ResultT result = track_fn(state, task);
232 result_queue_.push(std::move(result));
233 }
234 });
235 }
236 }
237
239 void submit(TaskT task)
240 {
241 task_queue_.push(WorkItem{std::move(task)});
242 }
243
245 ResultT collect()
246 {
247 return result_queue_.pop();
248 }
249
251 std::optional<ResultT> try_collect()
252 {
253 return result_queue_.try_pop();
254 }
255
257 void shutdown()
258 {
259 for (int i = 0; i < n_threads_; ++i)
260 task_queue_.push(WorkItem{PoolShutdownSentinel{}});
261 for (auto& t : threads_)
262 t.join();
263 threads_.clear();
264 }
265};
266
267
268
269} // namespace parallel
270} // namespace bertini
A simple thread-safe (mutex + condition-variable) FIFO queue.
Definition thread_pool.hpp:141
T pop()
Block until an item is available, then pop and return it.
Definition thread_pool.hpp:158
std::optional< T > try_pop()
Pop and return an item if one is ready, otherwise std::nullopt (non-blocking).
Definition thread_pool.hpp:168
void push(T item)
Push an item onto the queue and wake one waiter.
Definition thread_pool.hpp:148
WorkerThreadPool(int n_threads, StateFactory state_factory, TrackFn track_fn)
Construct the pool and start n_threads worker threads, each with its own state.
Definition thread_pool.hpp:212
void shutdown()
Signal all threads to exit and join (call only after all results are collected).
Definition thread_pool.hpp:257
ResultT collect()
Block until one result is available, then return it.
Definition thread_pool.hpp:245
void submit(TaskT task)
Submit a task to be tracked by a worker thread.
Definition thread_pool.hpp:239
std::optional< ResultT > try_collect()
Return a result if one is ready, otherwise std::nullopt (non-blocking).
Definition thread_pool.hpp:251
Definition initialize_finalize.hpp:43
unsigned AvailableCpuCount()
The number of CPUs this process may run on.
Definition thread_pool.hpp:89
unsigned EffectiveThreadCount(unsigned configured=0)
Resolve a configured thread count to the number of worker threads to actually spawn.
Definition thread_pool.hpp:122
Definition algorithm_builder.hpp:36
Definition thread_pool.hpp:180