Foundation
Loading...
Searching...
No Matches
ThreadPool.hpp
Go to the documentation of this file.
1#pragma once
2#include "AtomicQueue.hpp"
3#include "Logging.hpp"
4#include "Thread.hpp"
5#include <algorithm>
6#include <array>
7#include <cmath>
8#include <iterator>
9#include <type_traits>
10namespace Foundation::Core
11{
12 enum class JobPriority : size_t
13 {
14 Low,
15 Normal,
16 High,
17 };
18 inline constexpr size_t kJobPriorityCount = static_cast<size_t>(JobPriority::High) + 1;
19 struct Job
20 {
21 virtual ~Job() = default;
22 virtual void Execute(size_t id) noexcept = 0;
23 };
24 template <typename Lambda, typename ReturnType, typename... Args>
25 struct LambdaJob final : public Job
26 {
27 Lambda mFunc;
29 LambdaJob(Lambda&& func) : mFunc(std::forward<Lambda>(func)) {}
30 void Execute(size_t) noexcept override
31 {
32 if constexpr (std::is_same_v<ReturnType, void>)
33 {
34 mFunc();
35 mPromise.set_value();
36 }
37 else
38 mPromise.set_value(mFunc());
39 }
40 };
41
46 using JobQueues = std::array<JobQueue, kJobPriorityCount>;
51 {
57 size_t mSubmitting{};
63
65 // Ensure threads are joined first on destruction
67 void ThreadPoolWorker(size_t id);
68 bool BeginSubmit() noexcept
69 {
70 std::lock_guard lock(mSubmitMutex);
71 if (!mAccepting.load(std::memory_order_relaxed))
72 return false;
74 return true;
75 }
76 void EndSubmit() noexcept
77 {
78 std::lock_guard lock(mSubmitMutex);
79 if (--mSubmitting == 0)
80 mSubmitCV.notify_all();
81 }
82 static constexpr size_t PriorityIndex(JobPriority priority) noexcept { return static_cast<size_t>(priority); }
83 template <typename T, typename... Args>
84 requires std::is_base_of_v<Job, T>
85 void PushImplInternal(JobPriority priority, Allocator* jobAllocator, Args&&... args)
86 {
87 bool const submitting = BeginSubmit();
88 CHECK_MSG(submitting, "ThreadPool shutting down");
89 if (!submitting)
90 return;
91 CHECK_MSG(!mThreads.empty(), "ThreadPool has no worker threads");
92 CHECK_MSG(PriorityIndex(priority) < kJobPriorityCount, "Invalid job priority");
93 if (mThreads.empty() || PriorityIndex(priority) >= kJobPriorityCount)
94 {
95 EndSubmit();
96 return;
97 }
98 Allocator* allocator = jobAllocator ? jobAllocator : mAllocator;
99 auto task = ConstructUniqueBase<Job, T>(allocator, std::forward<Args>(args)...);
100 mTotal.fetch_add(1, std::memory_order_release);
101 bool const queued = mJobs[PriorityIndex(priority)].Push(std::move(task));
102 CHECK_MSG(queued, "ThreadPool job queue full");
103 if (!queued)
104 {
105 mTotal.fetch_sub(1, std::memory_order_release);
106 EndSubmit();
107 return;
108 }
109 mWakeEpoch.fetch_add(1, std::memory_order_release);
110 mWakeEpoch.notify_one();
111 EndSubmit();
112 }
113 template <typename Lambda, typename... Args>
114 auto PushLambdaInternal(JobPriority priority, Allocator* jobAllocator, Lambda&& func, Args const&... args)
115 {
116 auto LambdaFn = [func = std::forward<Lambda>(func), ... args = args] { return func(args...); };
117 using LambdaType = decltype(LambdaFn);
118 using ReturnType = decltype(LambdaFn());
119 LambdaJob<LambdaType, ReturnType> job(std::forward<LambdaType>(LambdaFn));
120 auto fut = job.mPromise.get_future();
121 PushImplInternal<LambdaJob<LambdaType, ReturnType>>(priority, jobAllocator, std::move(job));
122 return std::move(fut);
123 }
124
125 public:
126 ThreadPool(size_t numThreads, size_t maxTasks, Allocator* alloc, StringView name = "ThreadPool");
127 template <typename T, typename... Args>
128 requires std::is_base_of_v<Job, T>
129 void PushImpl(JobPriority priority, Args&&... args)
130 {
131 PushImplInternal<T>(priority, nullptr, std::forward<Args>(args)...);
132 }
133 template <typename T, typename... Args>
134 requires std::is_base_of_v<Job, T>
135 void PushImpl(Args&&... args)
136 {
137 PushImplInternal<T>(JobPriority::Normal, nullptr, std::forward<Args>(args)...);
138 }
143 template <typename T, typename... Args>
144 requires std::is_base_of_v<Job, T>
145 void PushImplAlloc(Allocator* jobAllocator, Args&&... args)
146 {
147 PushImplInternal<T>(JobPriority::Normal, jobAllocator, std::forward<Args>(args)...);
148 }
149 template <typename T, typename... Args>
150 requires std::is_base_of_v<Job, T>
151 void PushImplAlloc(JobPriority priority, Allocator* jobAllocator, Args&&... args)
152 {
153 PushImplInternal<T>(priority, jobAllocator, std::forward<Args>(args)...);
154 }
159 template <typename Lambda, typename... Args>
160 auto Push(JobPriority priority, Lambda&& func, Args const&... args)
161 {
162 return PushLambdaInternal(priority, nullptr, std::forward<Lambda>(func), args...);
163 }
164 template <typename Lambda, typename... Args>
165 auto Push(Lambda&& func, Args const&... args)
166 {
167 return PushLambdaInternal(JobPriority::Normal, nullptr, std::forward<Lambda>(func), args...);
168 }
173 template <typename Lambda, typename... Args>
174 auto PushAlloc(Allocator* jobAllocator, Lambda&& func, Args const&... args)
175 {
176 return PushLambdaInternal(JobPriority::Normal, jobAllocator, std::forward<Lambda>(func), args...);
177 }
178 template <typename Lambda, typename... Args>
179 auto PushAlloc(JobPriority priority, Allocator* jobAllocator, Lambda&& func, Args const&... args)
180 {
181 return PushLambdaInternal(priority, jobAllocator, std::forward<Lambda>(func), args...);
182 }
183
185 [[nodiscard]] size_t GetWorkerCount() const noexcept { return mThreads.size(); }
186
191 [[nodiscard]] size_t GetParallelForConcurrency() const noexcept { return mThreads.size() + 1; }
192
196 void Shutdown();
201 void Join();
203 ~ThreadPool();
204
205 [[nodiscard]] size_t GetPendingJobCount() const noexcept
206 {
207 return mTotal.load(std::memory_order_relaxed) - mComplete.load(std::memory_order_relaxed);
208 }
209 [[nodiscard]] size_t GetCompletedJobCount() const noexcept { return mComplete.load(std::memory_order_relaxed); }
210 [[nodiscard]] size_t GetTotalJobCount() const noexcept { return mTotal.load(std::memory_order_relaxed); }
211
215 const static size_t CalcTaskSize(size_t size) { return std::bit_ceil(std::max<size_t>(size, 1)); }
216 };
217} // namespace Foundation::Core
#define CHECK_MSG(expr, format_str,...)
Allocator interface (noexcept)
Definition Allocator.hpp:29
Atomic, bounded multi-producer multi-consumer FIFO ring buffer with a fixed maximum size.
Definition AtomicQueue.hpp:85
Atomic, lock-free Thread Pool implementation with fixed bounds.
Definition ThreadPool.hpp:51
size_t GetTotalJobCount() const noexcept
Definition ThreadPool.hpp:210
auto Push(JobPriority priority, Lambda &&func, Args const &... args)
Push a lambda job to the thread pool.
Definition ThreadPool.hpp:160
static constexpr size_t PriorityIndex(JobPriority priority) noexcept
Definition ThreadPool.hpp:82
Atomic< bool > mAccepting
Definition ThreadPool.hpp:54
auto PushLambdaInternal(JobPriority priority, Allocator *jobAllocator, Lambda &&func, Args const &... args)
Definition ThreadPool.hpp:114
size_t GetParallelForConcurrency() const noexcept
Number of distinct worker ids a ParallelFor functor may see (workers + the participating caller)....
Definition ThreadPool.hpp:191
Allocator * mAllocator
Definition ThreadPool.hpp:52
~ThreadPool()
Join accepted jobs and stop all workers.
Definition ThreadPool.cpp:65
static const size_t CalcTaskSize(size_t size)
Definition ThreadPool.hpp:215
void PushImpl(JobPriority priority, Args &&... args)
Definition ThreadPool.hpp:129
void Shutdown()
Stop accepting work, drain accepted jobs, and stop all workers.
Definition ThreadPool.cpp:26
Atomic< size_t > mProgressEpoch
Definition ThreadPool.hpp:60
void PushImplInternal(JobPriority priority, Allocator *jobAllocator, Args &&... args)
Definition ThreadPool.hpp:85
bool BeginSubmit() noexcept
Definition ThreadPool.hpp:68
auto Push(Lambda &&func, Args const &... args)
Definition ThreadPool.hpp:165
Atomic< size_t > mComplete
Definition ThreadPool.hpp:61
void PushImpl(Args &&... args)
Definition ThreadPool.hpp:135
String mName
Definition ThreadPool.hpp:53
size_t mSubmitting
Definition ThreadPool.hpp:57
Atomic< size_t > mTotal
Definition ThreadPool.hpp:62
Atomic< bool > mShutdown
Definition ThreadPool.hpp:58
size_t GetWorkerCount() const noexcept
Number of worker threads. Worker ids passed to Execute are in [0, this).
Definition ThreadPool.hpp:185
void PushImplAlloc(Allocator *jobAllocator, Args &&... args)
Push a job with an explicit allocator for the job object.
Definition ThreadPool.hpp:145
size_t GetCompletedJobCount() const noexcept
Definition ThreadPool.hpp:209
Vector< Thread > mThreads
Definition ThreadPool.hpp:66
auto PushAlloc(JobPriority priority, Allocator *jobAllocator, Lambda &&func, Args const &... args)
Definition ThreadPool.hpp:179
void Join()
Wait for all scheduled jobs to complete.
Definition ThreadPool.cpp:56
void ThreadPoolWorker(size_t id)
Definition ThreadPool.cpp:74
void EndSubmit() noexcept
Definition ThreadPool.hpp:76
Mutex mSubmitMutex
Definition ThreadPool.hpp:55
auto PushAlloc(Allocator *jobAllocator, Lambda &&func, Args const &... args)
Push a lambda job with an explicit allocator for the job object.
Definition ThreadPool.hpp:174
CondVar mSubmitCV
Definition ThreadPool.hpp:56
void PushImplAlloc(JobPriority priority, Allocator *jobAllocator, Args &&... args)
Definition ThreadPool.hpp:151
Atomic< size_t > mWakeEpoch
Definition ThreadPool.hpp:59
size_t GetPendingJobCount() const noexcept
Definition ThreadPool.hpp:205
JobQueues mJobs
Definition ThreadPool.hpp:64
Lock-free atomic primitives and implementations of data structures.
Definition Allocator.hpp:6
std::vector< T, StlAllocator< T > > Vector
std::vector with explicit Foundation::Core::StlAllocator constructor
Definition Container.hpp:149
std::mutex Mutex
Definition Thread.hpp:10
JobPriority
Definition ThreadPool.hpp:13
std::array< JobQueue, kJobPriorityCount > JobQueues
Definition ThreadPool.hpp:46
std::condition_variable CondVar
Definition Thread.hpp:9
std::atomic< T > Atomic
Alias of std::atomic<T>.
Definition Atomic.hpp:26
std::basic_string< char, std::char_traits< char >, StlDefaultAllocator< char > > String
Alias for std::basic_string<char>, without an explicit allocator constructor.
Definition Container.hpp:120
constexpr size_t kJobPriorityCount
Definition ThreadPool.hpp:18
std::basic_string_view< char > StringView
Alias for std::basic_string_view<char>
Definition Container.hpp:56
std::promise< T > Promise
Definition Thread.hpp:6
Definition ThreadPool.hpp:20
virtual ~Job()=default
virtual void Execute(size_t id) noexcept=0
Definition ThreadPool.hpp:26
void Execute(size_t) noexcept override
Definition ThreadPool.hpp:30
LambdaJob(Lambda &&func)
Definition ThreadPool.hpp:29
Lambda mFunc
Definition ThreadPool.hpp:27
Promise< ReturnType > mPromise
Definition ThreadPool.hpp:28