5#include <condition_variable>
16#if defined(__unix__) || defined(__APPLE__)
18#define WMTK_WORKER_POOL_RESET_ON_FORK 1
21namespace wmtk::threading {
29 virtual ~job() =
default;
31 virtual void run() noexcept = 0;
76#ifdef WMTK_WORKER_POOL_RESET_ON_FORK
77 static const bool registered = (pthread_atfork(
nullptr,
nullptr, &abandon_in_child),
true);
80 auto& slot = current();
81 worker_pool* p = slot.load(std::memory_order_acquire);
82 if (p !=
nullptr)
return *p;
86 if (slot.compare_exchange_strong(p, fresh, std::memory_order_acq_rel))
return *fresh;
95 std::unique_lock<std::mutex> lock(m_mutex);
102 if (m_jobs.size() + m_spawning < m_idle) {
103 m_jobs.push_back(std::move(j));
114 std::thread([
this] { work(); }).detach();
116 std::lock_guard<std::mutex> lock(m_mutex);
121 std::lock_guard<std::mutex> lock(m_mutex);
124 m_jobs.push_back(std::move(j));
134 std::unique_lock<std::mutex> lock(m_mutex);
135 if (m_jobs.size() + m_spawning >= m_idle)
return false;
136 m_jobs.push_back(std::move(j));
145 std::lock_guard<std::mutex> lock(m_mutex);
152 static std::atomic<worker_pool*>& current()
154 static std::atomic<worker_pool*> pool{
nullptr};
158#ifdef WMTK_WORKER_POOL_RESET_ON_FORK
162 static void abandon_in_child() { current().store(
nullptr, std::memory_order_relaxed); }
165 [[noreturn]]
void work()
167 std::unique_lock<std::mutex> lock(m_mutex);
170 m_cv.wait(lock, [
this] {
return !m_jobs.empty(); });
172 std::unique_ptr<job> j = std::move(m_jobs.front());
179 std::fesetround(FE_TONEAREST);
194 std::condition_variable m_cv;
195 std::deque<std::unique_ptr<job>> m_jobs;
197 size_t m_spawning = 0;
198 size_t m_workers = 0;
212 std::condition_variable m_done;
213 size_t m_pending = 0;
214 std::exception_ptr m_eptr;
221 template <
typename F>
227 auto j = std::make_unique<task_job<std::decay_t<F>>>(*
this, std::forward<F>(f));
229 std::lock_guard<std::mutex> lock(m_mutex);
233 detail::worker_pool::instance().submit(std::move(j));
236 std::lock_guard<std::mutex> lock(m_mutex);
250 template <
typename F>
253 std::unique_ptr<detail::job> j =
254 std::make_unique<task_job<std::decay_t<F>>>(*
this, std::forward<F>(f));
256 std::lock_guard<std::mutex> lock(m_mutex);
259 if (detail::worker_pool::instance().try_submit(j))
return true;
261 std::lock_guard<std::mutex> lock(m_mutex);
268 std::unique_lock<std::mutex> lock(m_mutex);
269 m_done.wait(lock, [
this] {
return m_pending == 0; });
271 std::exception_ptr e = m_eptr;
274 std::rethrow_exception(e);
280 std::unique_lock<std::mutex> lock(m_mutex);
281 m_done.wait(lock, [
this] {
return m_pending == 0; });
286 template <
typename Fn>
290 std::optional<Fn> m_fn;
293 template <
typename G>
296 , m_fn(std::in_place, std::forward<G>(fn))
299 void run() noexcept
override
304 m_group.record(std::current_exception());
312 void finish() noexcept
override { m_group.finish_one(); }
315 void record(std::exception_ptr e)
317 std::lock_guard<std::mutex> lock(m_mutex);
319 m_eptr = std::move(e);
327 std::lock_guard<std::mutex> lock(m_mutex);
328 if (--m_pending == 0) {
The process-wide set of threads that task_group runs its tasks on.
Definition task_group.hpp:72
void submit(std::unique_ptr< job > j)
Run job on a worker. Throws (and queues nothing) if a needed worker cannot be started.
Definition task_group.hpp:92
bool try_submit(std::unique_ptr< job > &j)
Definition task_group.hpp:132
size_t worker_count()
How many workers the pool has started. For tests.
Definition task_group.hpp:143
A task of this group, queued on the pool. Move-only tasks are fine.
Definition task_group.hpp:288
void run() noexcept override
Runs the task and destroys its state. Never throws.
Definition task_group.hpp:299
void finish() noexcept override
Definition task_group.hpp:312
Definition task_group.hpp:210
bool try_run(F &&f)
As run(), but only on a worker of the pool that is idle right now. Returns false, having run nothing,...
Definition task_group.hpp:251
Definition task_group.hpp:28
virtual void finish() noexcept=0
virtual void run() noexcept=0
Runs the task and destroys its state. Never throws.