135class ScopedThreadPhaseExecutor {
137 explicit ScopedThreadPhaseExecutor(std::size_t worker_count) noexcept
138 : worker_count_(worker_count == 0 ? 1 : worker_count) {}
140 ScopedThreadPhaseExecutor() noexcept
141 : ScopedThreadPhaseExecutor(std::thread::hardware_concurrency()) {}
143 [[nodiscard]]
auto worker_count()
const noexcept -> std::size_t {
144 return worker_count_;
147 template <
typename Fn>
148 auto for_each_operation(std::size_t first, std::size_t count, Fn&& fn)
const
154 const auto thread_count = std::min(worker_count_, count);
155 TESS_DIAG_EVENT_VALUE(queued_scoped_thread_dispatch, thread_count);
156 std::atomic<std::size_t> next_offset = 0;
157 std::atomic<bool> cancelled =
false;
158 std::exception_ptr exception;
159 std::mutex exception_mutex;
160 std::vector<PlannedExecutionResult> results(count);
161 std::vector<std::thread> threads;
162 threads.reserve(thread_count);
163 auto&& callback = fn;
165 for (std::size_t worker = 0; worker < thread_count; ++worker) {
167 threads.emplace_back([&] {
169 const auto offset = next_offset.fetch_add(1);
170 if (offset >= count || cancelled.load(std::memory_order_acquire)) {
174 results[offset] = callback(first + offset);
177 const std::scoped_lock lock{exception_mutex};
179 exception = std::current_exception();
182 cancelled.store(
true, std::memory_order_release);
192 for (
auto& thread : threads) {
199 for (
auto& thread : threads) {
204 std::rethrow_exception(exception);
207 for (
const auto result : results) {
208 if (result.status != PlannedExecutionStatus::Executed) {
216 std::size_t worker_count_ = 1;
256class WorkerPoolPhaseExecutor {
258 explicit WorkerPoolPhaseExecutor(std::size_t worker_count) {
259 const auto count = worker_count == 0 ? std::size_t{1} : worker_count;
260 workers_.reserve(count);
262 for (std::size_t worker = 0; worker < count; ++worker) {
263 workers_.emplace_back([
this] { run_worker(); });
271 const std::scoped_lock lock{mutex_};
274 work_cv_.notify_all();
275 for (
auto& worker : workers_) {
282 WorkerPoolPhaseExecutor()
283 : WorkerPoolPhaseExecutor(std::thread::hardware_concurrency()) {}
285 WorkerPoolPhaseExecutor(
const WorkerPoolPhaseExecutor&) =
delete;
286 auto operator=(
const WorkerPoolPhaseExecutor&)
287 -> WorkerPoolPhaseExecutor& =
delete;
288 WorkerPoolPhaseExecutor(WorkerPoolPhaseExecutor&&) =
delete;
289 auto operator=(WorkerPoolPhaseExecutor&&)
290 -> WorkerPoolPhaseExecutor& =
delete;
292 ~WorkerPoolPhaseExecutor() {
294 const std::scoped_lock lock{mutex_};
297 work_cv_.notify_all();
298 for (
auto& worker : workers_) {
303 [[nodiscard]]
auto worker_count()
const noexcept -> std::size_t {
304 return workers_.size();
311 void reserve_operations(std::size_t count)
const {
312 const std::scoped_lock lock{mutex_};
313 TESS_ASSERT_MSG(!dispatch_active_,
314 "WorkerPoolPhaseExecutor::reserve_operations called "
315 "during an active dispatch");
316 if (results_.size() < count) {
317 results_.resize(count);
321 template <
typename Fn>
322 auto for_each_operation(std::size_t first, std::size_t count, Fn&& fn)
const
327 TESS_DIAG_EVENT_VALUE(queued_worker_pool_dispatch,
328 std::min(workers_.size(), count));
330 auto&& callback = fn;
331 using Callback = std::remove_reference_t<
decltype(callback)>;
332 std::size_t runs = 0;
333 std::exception_ptr exception;
335 const std::scoped_lock lock{mutex_};
339 TESS_ASSERT_MSG(!dispatch_active_,
340 "WorkerPoolPhaseExecutor::for_each_operation "
341 "re-entered during an active dispatch");
342 if (results_.size() < count) {
343 results_.resize(count);
349 dispatch_active_ =
true;
350 job_context_ = &callback;
351 job_invoke_ = [](
void* context,
353 return (*
static_cast<Callback*
>(context))(index);
360 job_stride_ = std::max<std::size_t>(
361 1, count / (std::max<std::size_t>(1, workers_.size()) * 4));
362 next_offset_.store(0, std::memory_order_relaxed);
363 finished_operations_.store(0, std::memory_order_relaxed);
364 job_cancelled_.store(
false, std::memory_order_relaxed);
365 job_exception_ = std::exception_ptr{};
371 runs = (count + job_stride_ - 1) / job_stride_;
378 if (runs >= workers_.size()) {
379 work_cv_.notify_all();
381 for (std::size_t i = 0; i < runs; ++i) {
382 work_cv_.notify_one();
387 std::unique_lock lock{mutex_};
388 done_cv_.wait(lock, [&] {
389 return active_workers_ == 0 &&
391 finished_operations_.load(std::memory_order_acquire) == count);
393 exception = job_exception_;
395 dispatch_active_ =
false;
399 std::rethrow_exception(exception);
402 for (std::size_t offset = 0; offset < count; ++offset) {
403 if (results_[offset].status != PlannedExecutionStatus::Executed) {
404 return results_[offset];
414 std::uint64_t seen_epoch = 0;
416 std::unique_lock lock{mutex_};
417 work_cv_.wait(lock, [&] {
418 return stop_ || (job_active_ && job_epoch_ != seen_epoch);
423 seen_epoch = job_epoch_;
425 auto*
const context = job_context_;
426 const auto invoke = job_invoke_;
427 const auto first = job_first_;
428 const auto count = job_count_;
429 const auto stride = job_stride_;
432 auto cancelled =
false;
433 while (!job_cancelled_.load(std::memory_order_acquire)) {
435 next_offset_.fetch_add(stride, std::memory_order_relaxed);
436 if (begin >= count) {
439 const auto end = std::min(begin + stride, count);
440 auto finished = std::size_t{0};
441 for (
auto offset = begin; offset < end; ++offset) {
442 if (job_cancelled_.load(std::memory_order_acquire)) {
447 results_[offset] = invoke(context, first + offset);
450 job_cancelled_.store(
true, std::memory_order_release);
452 const std::scoped_lock exception_lock{mutex_};
453 if (!job_exception_) {
454 job_exception_ = std::current_exception();
463 finished_operations_.fetch_add(finished, std::memory_order_release);
475 if (active_workers_ == 0) {
476 done_cv_.notify_one();
481 mutable std::mutex mutex_;
482 mutable std::condition_variable work_cv_;
483 mutable std::condition_variable done_cv_;
484 mutable std::vector<PlannedExecutionResult> results_;
491 alignas(128)
mutable std::atomic<std::size_t> next_offset_ = 0;
492 alignas(128)
mutable std::atomic<std::size_t> finished_operations_ = 0;
493 mutable std::atomic<bool> job_cancelled_ =
false;
494 alignas(128)
mutable void* job_context_ =
nullptr;
495 mutable JobInvoke job_invoke_ =
nullptr;
496 mutable std::size_t job_first_ = 0;
497 mutable std::size_t job_count_ = 0;
498 mutable std::size_t job_stride_ = 1;
499 mutable std::uint64_t job_epoch_ = 0;
500 mutable std::size_t active_workers_ = 0;
501 mutable std::exception_ptr job_exception_;
502 mutable bool job_active_ =
false;
503 mutable bool dispatch_active_ =
false;
505 std::vector<std::thread> workers_;