19#include <condition_variable>
26#include <moodycamel/concurrentqueue.h>
30#include <dispenso/detail/math.h>
31#include <dispenso/detail/per_thread_info.h>
32#include <dispenso/detail/thread_pool_wake.h>
49T* consumeLoad(std::atomic<T*>& ptr) {
50 T* p = ptr.load(std::memory_order_relaxed);
51 DISPENSO_TSAN_ANNOTATE_HAPPENS_AFTER(&ptr);
57template <
typename Result>
59template <
typename Result>
63#if !defined(DISPENSO_WAKEUP_ENABLE)
64#if defined(_WIN32) || defined(__linux__) || defined(__MACH__)
65#define DISPENSO_WAKEUP_ENABLE 1
67#define DISPENSO_WAKEUP_ENABLE 0
74#if !defined(DISPENSO_POLL_PERIOD_US)
76#define DISPENSO_POLL_PERIOD_US 1000
78#define DISPENSO_POLL_PERIOD_US 200
87#if !defined(DISPENSO_WAKE_BACKSTOP_US)
88#define DISPENSO_WAKE_BACKSTOP_US 100000
91constexpr bool kDefaultWakeupEnable = DISPENSO_WAKEUP_ENABLE;
93constexpr uint32_t kDefaultSleepLenUs =
94 kDefaultWakeupEnable ? DISPENSO_WAKE_BACKSTOP_US : DISPENSO_POLL_PERIOD_US;
101struct ForceQueuingTag {};
117 DISPENSO_DLL_ACCESS
ThreadPool(
size_t n,
size_t poolLoadMultiplier = 32);
137 template <
class Rep,
class Period>
140 const std::chrono::duration<Rep, Period>& sleepDuration =
141 std::chrono::microseconds(kDefaultSleepLenUs)) {
144 static_cast<uint32_t
>(
145 std::chrono::duration_cast<std::chrono::microseconds>(sleepDuration).count()));
154 DISPENSO_DLL_ACCESS
void resize(ssize_t n) DISPENSO_NO_THREAD_SAFETY_ANALYSIS {
155 std::lock_guard<std::mutex> lk(threadsMutex_);
166 return numThreads_.load(std::memory_order_relaxed);
177 template <
typename F>
178 DISPENSO_REQUIRES(OnceCallableFunc<F>)
179 void schedule(F&& f);
189 template <
typename F>
190 DISPENSO_REQUIRES(OnceCallableFunc<F>)
191 void schedule(F&& f, ForceQueuingTag);
205 template <
typename Generator>
206 void scheduleBulk(
size_t count, Generator&& gen);
235 pool_->keepAwakeCount_.fetch_add(1, std::memory_order_acq_rel);
242 other.pool_ =
nullptr;
247 other.pool_ =
nullptr;
256 pool_->keepAwakeCount_.fetch_sub(1, std::memory_order_release);
275 class PerThreadData {
277 void setThread(std::thread&& t);
286 std::atomic<bool> running_{
true};
289 DISPENSO_DLL_ACCESS uint32_t waitOnThread(int32_t threadIdx, uint32_t priorEpoch);
291 void setSignalingWake(
bool enable, uint32_t sleepDurationUs) DISPENSO_NO_THREAD_SAFETY_ANALYSIS {
292 std::lock_guard<std::mutex> lk(threadsMutex_);
293 ssize_t currentPoolSize = numThreads();
295 enableEpochWaiter_.store(enable, std::memory_order_release);
296 sleepLengthUs_.store(sleepDurationUs, std::memory_order_release);
297 resizeLocked(currentPoolSize);
300 DISPENSO_DLL_ACCESS
void resizeLocked(ssize_t n);
302 void executeNext(OnceFunction work);
304 template <
bool kUseWakeSleep>
305 void threadLoopImpl(PerThreadData& threadData, int32_t ringIndex);
307 void threadLoopWake(PerThreadData& threadData, int32_t ringIndex) {
308 threadLoopImpl<true>(threadData, ringIndex);
310 void threadLoopPoll(PerThreadData& threadData, int32_t ringIndex) {
311 threadLoopImpl<false>(threadData, ringIndex);
314 void markWorkDone(
bool& isWorking);
315 void markIdle(
bool& isWorking);
317 bool tryExecuteNext();
318 bool tryExecuteNextFromProducerToken(moodycamel::ProducerToken& token);
319 bool tryExecuteNextFromRings(
size_t& startRing);
322 DISPENSO_INLINE
bool shouldRunInline();
325 DISPENSO_INLINE
void scheduleImpl(OnceFunction task, moodycamel::ProducerToken* token);
329 DISPENSO_INLINE
void scheduleImplPlaced(OnceFunction task, moodycamel::ProducerToken* token);
333 template <
bool kPlaced,
typename F>
334 inline void forceEnqueue(F&& f, moodycamel::ProducerToken* token);
336 template <
typename F>
337 void schedule(moodycamel::ProducerToken& token, F&& f);
339 template <
typename F>
340 void schedule(moodycamel::ProducerToken& token, F&& f, ForceQueuingTag);
342 template <
typename F>
343 void schedulePlaced(moodycamel::ProducerToken& token, F&& f);
345 template <
typename F>
346 void schedulePlaced(moodycamel::ProducerToken& token, F&& f, ForceQueuingTag);
349 template <
typename F>
350 DISPENSO_REQUIRES(OnceCallableFunc<F>)
351 void schedulePlaced(F&& f);
353 template <
typename F>
354 DISPENSO_REQUIRES(OnceCallableFunc<F>)
355 void schedulePlaced(F&& f, ForceQueuingTag);
359 template <
bool kPlaced,
typename Generator>
360 void scheduleBulkImpl(
size_t count, Generator&& gen);
364 template <
typename Generator>
365 void scheduleBulkPlaced(
size_t count, Generator&& gen);
370 template <
typename Generator>
372 scheduleBulkEnqueue(
size_t count, Generator&& gen, moodycamel::ProducerToken* token =
nullptr);
378 void conditionallyWake() {
379 auto* ws = detail::consumeLoad(wakeState_);
380 if (enableEpochWaiter_.load(std::memory_order_acquire) && ws) {
381 int32_t sleeping = ws->totalSleeping();
383 ssize_t pending = workRemaining_.load(std::memory_order_relaxed);
384 ssize_t numT = numThreads_.load(std::memory_order_relaxed);
385 ssize_t awake = numT -
static_cast<ssize_t
>(sleeping);
386 if (pending > awake) {
387 ws->claimAndWakeOne();
395#if __cplusplus < 201703L
396 static void*
operator new(
size_t sz) {
397 return detail::alignedMalloc(sz);
399 static void operator delete(
void* ptr) {
400 return detail::alignedFree(ptr);
407 using Ring = MpmcRingBuffer<OnceFunction, 16>;
411 static constexpr size_t kStealSlotsPerThread = 4;
413#if defined(DISPENSO_TUNE_STEAL_RING_SHARING)
414 static constexpr size_t kStealRingSharing = DISPENSO_TUNE_STEAL_RING_SHARING;
417 static constexpr size_t kStealRingSharing = 8;
419 static constexpr size_t kStealRingCapacity = kStealSlotsPerThread * kStealRingSharing;
420 using StealRing = MpmcRingBuffer<OnceFunction, kStealRingCapacity>;
427#if defined(DISPENSO_TUNE_CROSS_RING_FAIL_THRESHOLD)
428 static constexpr int kCrossRingFailThreshold = DISPENSO_TUNE_CROSS_RING_FAIL_THRESHOLD;
430 static constexpr int kCrossRingFailThreshold = 32;
440 DISPENSO_INLINE
void enqueueToCentralQueue(OnceFunction task, moodycamel::ProducerToken* token) {
441 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN();
444 enqueued = work_.enqueue(*token, std::move(task));
446 enqueued = work_.enqueue(std::move(task));
448 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_END();
449 if (DISPENSO_EXPECT(!enqueued,
false)) {
450#if defined(__cpp_exceptions)
451 throw std::bad_alloc();
457 centralQueueNonEmpty_.store(
true, std::memory_order_relaxed);
464 template <
typename Generator>
465 void scheduleBulkToRings(
size_t count, Generator&& gen, moodycamel::ProducerToken* fallbackToken);
467 template <
typename Generator>
468 DISPENSO_INLINE
void scheduleBulkToRingsFastPath(
472 moodycamel::ProducerToken* fallbackToken);
474 template <
typename Generator>
475 DISPENSO_INLINE
void scheduleBulkToRingsBatched(
480 moodycamel::ProducerToken* fallbackToken);
488 DISPENSO_INLINE
bool tryFindAndExecuteWork(
490 StealRing& myStealRing,
492 moodycamel::ConsumerToken& ctoken,
495 bool checkQueue =
true);
501 static constexpr int kWorkBatchSize = 8;
506 static constexpr int32_t kSpinnerWakeThreshold = 2;
508 mutable std::mutex threadsMutex_;
509 std::deque<PerThreadData> threads_;
510 size_t poolLoadMultiplier_;
515 std::atomic<ssize_t> numThreads_;
517 moodycamel::ConcurrentQueue<OnceFunction> work_;
527 alignas(
kCacheLineSize) std::atomic<bool> centralQueueNonEmpty_{
false};
539 alignas(
kCacheLineSize) std::atomic<bool> enableEpochWaiter_{kDefaultWakeupEnable};
540 std::atomic<uint32_t> sleepLengthUs_{kDefaultSleepLenUs};
562 std::atomic<detail::PoolWakeState*> wakeState_{
nullptr};
563 std::vector<decltype(detail::makeAligned<detail::PoolWakeState>(0))> wakeStateGraveyard_;
568 ConcurrentObjectArena<Ring> rings_;
569 std::atomic<size_t> numRings_{0};
577 ConcurrentObjectArena<StealRing> stealRings_;
578 std::atomic<size_t> numStealRings_{0};
579 size_t stealRingSharing_{kStealRingSharing};
591 static constexpr size_t kMaxStealRings = 64;
592 alignas(
kCacheLineSize) std::atomic<uint64_t> stealRingsWithWork_{0};
601#if defined DISPENSO_DEBUG
602 alignas(
kCacheLineSize) std::atomic<ssize_t> outstandingTaskSets_{0};
605 friend class ConcurrentTaskSet;
606 friend class TaskSet;
607 friend class TaskSetBase;
609 template <
typename Result>
610 friend class detail::FutureBase;
611 template <
typename Result>
612 friend class detail::FutureImplBase;
631DISPENSO_INLINE
bool ThreadPool::shouldRunInline() {
632 ssize_t curWork = workRemaining_.load(std::memory_order_relaxed);
633 ssize_t quickLoadFactor = numThreads_.load(std::memory_order_relaxed);
634 quickLoadFactor += quickLoadFactor / 2;
635 return (detail::PerPoolPerThreadInfo::isPoolRecursive(
this) && curWork > quickLoadFactor) ||
636 (curWork > poolLoadFactor_.load(std::memory_order_relaxed));
639template <
bool kPlaced,
typename F>
640inline void ThreadPool::forceEnqueue(F&& f, moodycamel::ProducerToken* token) {
641 if (!numThreads_.load(std::memory_order_relaxed)) {
645 workRemaining_.fetch_add(1, std::memory_order_release);
647 scheduleImplPlaced({std::forward<F>(f)}, token);
649 scheduleImpl({std::forward<F>(f)}, token);
654DISPENSO_REQUIRES(OnceCallableFunc<F>)
656 if (shouldRunInline()) {
659 schedule(std::forward<F>(f), ForceQueuingTag());
664DISPENSO_REQUIRES(OnceCallableFunc<F>)
667 static_cast<moodycamel::ProducerToken*
>(detail::PerPoolPerThreadInfo::producer(
this));
668 forceEnqueue<false>(std::forward<F>(f), token);
673 if (shouldRunInline()) {
676 schedule(token, std::forward<F>(f), ForceQueuingTag());
682 forceEnqueue<false>(std::forward<F>(f), &token);
686DISPENSO_REQUIRES(OnceCallableFunc<F>)
687inline void ThreadPool::schedulePlaced(F&& f) {
688 if (shouldRunInline()) {
691 schedulePlaced(std::forward<F>(f), ForceQueuingTag());
696DISPENSO_REQUIRES(OnceCallableFunc<F>)
697inline void ThreadPool::schedulePlaced(F&& f, ForceQueuingTag) {
699 static_cast<moodycamel::ProducerToken*
>(detail::PerPoolPerThreadInfo::producer(
this));
700 forceEnqueue<true>(std::forward<F>(f), token);
704inline void ThreadPool::schedulePlaced(moodycamel::ProducerToken& token, F&& f) {
705 if (shouldRunInline()) {
708 schedulePlaced(token, std::forward<F>(f), ForceQueuingTag());
713inline void ThreadPool::schedulePlaced(moodycamel::ProducerToken& token, F&& f, ForceQueuingTag) {
714 forceEnqueue<true>(std::forward<F>(f), &token);
717DISPENSO_INLINE
void ThreadPool::scheduleImpl(OnceFunction task, moodycamel::ProducerToken* token) {
718 enqueueToCentralQueue(std::move(task), token);
725 auto* ws = detail::consumeLoad(wakeState_);
726 if (enableEpochWaiter_.load(std::memory_order_acquire) && ws) {
727 int32_t sleeping = ws->totalSleeping();
729 ssize_t pending = workRemaining_.load(std::memory_order_relaxed);
730 ssize_t numT = numThreads_.load(std::memory_order_relaxed);
731 ssize_t awake = numT -
static_cast<ssize_t
>(sleeping);
732 if (pending > awake) {
733 ws->claimAndWakeOne();
739DISPENSO_INLINE
void ThreadPool::scheduleImplPlaced(
741 moodycamel::ProducerToken* token) {
743 auto* ws = detail::consumeLoad(wakeState_);
744 if (enableEpochWaiter_.load(std::memory_order_acquire) && ws) {
745 int32_t sleeping = ws->totalSleeping();
747 numNotWorking_.load(std::memory_order_relaxed) - sleeping < kSpinnerWakeThreshold) {
748 int32_t wokeThread = ws->claimAndWakeOne();
749 if (wokeThread >= 0) {
750 size_t stealIdx =
static_cast<size_t>(wokeThread) / stealRingSharing_;
751 if (stealIdx < numStealRings_.load(std::memory_order_relaxed) &&
752 stealRings_[stealIdx].try_push(std::move(task))) {
753 if (stealIdx < kMaxStealRings) {
754 stealRingsWithWork_.fetch_or(uint64_t{1} << stealIdx, std::memory_order_release);
763 enqueueToCentralQueue(std::move(task), token);
768inline bool ThreadPool::tryExecuteNext() {
770 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN();
771 bool dequeued = work_.try_dequeue(next);
772 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_END();
774 executeNext(std::move(next));
780inline bool ThreadPool::tryExecuteNextFromProducerToken(moodycamel::ProducerToken& token) {
782 if (work_.try_dequeue_from_producer(token, next)) {
783 executeNext(std::move(next));
789inline bool ThreadPool::tryExecuteNextFromRings(
size_t& startRing) {
796 size_t n = numRings_.load(std::memory_order_acquire);
797 for (
size_t i = 0; i < n; ++i) {
798 size_t idx = (startRing + i) % n;
799 if (rings_[idx].try_pop(task)) {
801 executeNext(std::move(task));
809inline void ThreadPool::executeNext(OnceFunction next) {
811 workRemaining_.fetch_add(-1, std::memory_order_relaxed);
814DISPENSO_INLINE
bool ThreadPool::tryFindAndExecuteWork(
816 StealRing& myStealRing,
818 moodycamel::ConsumerToken& ctoken,
825 bool fromRing = myRing.try_pop(task);
830 if (checkQueue && centralQueueNonEmpty_.load(std::memory_order_relaxed)) {
831 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN();
832 bool got = work_.try_dequeue(ctoken, task);
833 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_END();
840 centralQueueNonEmpty_.store(
false, std::memory_order_relaxed);
842 if (!myStealRing.empty() && myStealRing.try_pop(task)) {
846 if (failCount >= kCrossRingFailThreshold) {
847 uint64_t mask = stealRingsWithWork_.load(std::memory_order_acquire);
849 if (myStealIdx < kMaxStealRings) {
850 mask &= ~(uint64_t{1} << myStealIdx);
853 int target = detail::countTrailingZeros(mask);
854 if (stealRings_[
static_cast<size_t>(target)].try_pop(task)) {
858 stealRingsWithWork_.fetch_and(~(uint64_t{1} << target), std::memory_order_relaxed);
863 if (checkQueue && centralQueueNonEmpty_.load(std::memory_order_relaxed)) {
864 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN();
865 bool got = work_.try_dequeue(ctoken, task);
866 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_END();
871 centralQueueNonEmpty_.store(
false, std::memory_order_relaxed);
873 bool fromRing = myRing.try_pop(task);
884template <
typename Generator>
885DISPENSO_INLINE
void ThreadPool::scheduleBulkToRingsFastPath(
889 moodycamel::ProducerToken* fallbackToken) {
890#if !defined(DISPENSO_DISABLE_CASCADE_WAKERANGE)
895 auto* wsCascade = detail::consumeLoad(wakeState_);
896 bool useCascade = enableEpochWaiter_.load(std::memory_order_acquire) && wsCascade &&
897 wsCascade->totalSleeping() > 0;
898 for (
size_t ring = 0; ring < count && ring < ringCount; ++ring) {
899 OnceFunction task = gen(ring);
900 int32_t target = useCascade
901 ? wsCascade->cascadeTargetFor(
static_cast<int32_t
>(ring),
static_cast<int32_t
>(count))
904 OnceFunction wrapped = [wsCascade, target, inner = std::move(task)]()
mutable {
905 wsCascade->cascadeWake(target);
908 if (!rings_[ring].try_push(std::move(wrapped))) {
909 enqueueToCentralQueue(std::move(wrapped), fallbackToken);
912 if (!rings_[ring].try_push(std::move(task))) {
913 enqueueToCentralQueue(std::move(task), fallbackToken);
918 for (
size_t ring = 0; ring < count && ring < ringCount; ++ring) {
919 OnceFunction task = gen(ring);
920 if (!rings_[ring].try_push(std::move(task))) {
921 enqueueToCentralQueue(std::move(task), fallbackToken);
927template <
typename Generator>
928DISPENSO_INLINE
void ThreadPool::scheduleBulkToRingsBatched(
933 moodycamel::ProducerToken* fallbackToken) {
936 for (
size_t ring = 0; ring < ringCount && taskIdx < count; ++ring) {
937 size_t blockEnd = std::min(taskIdx + tasksPerRing, count);
938 size_t blockSize = blockEnd - taskIdx;
940 size_t toStage = std::min(blockSize, kMaxStage);
941 OnceFunction staged[kMaxStage];
942 for (
size_t j = 0; j < toStage; ++j) {
943 staged[j] = gen(taskIdx + j);
946 size_t pushed = rings_[ring].try_push_batch(staged, toStage);
948 for (
size_t j = pushed; j < toStage; ++j) {
949 enqueueToCentralQueue(std::move(staged[j]), fallbackToken);
952 for (
size_t j = taskIdx + toStage; j < blockEnd; ++j) {
953 enqueueToCentralQueue(gen(j), fallbackToken);
955 taskIdx += blockSize;
959template <
typename Generator>
960void ThreadPool::scheduleBulkToRings(
963 moodycamel::ProducerToken* fallbackToken) {
967 assert(count <= numRings_.load(std::memory_order_relaxed));
969 workRemaining_.fetch_add(
static_cast<ssize_t
>(count), std::memory_order_release);
974 size_t ringCount = numRings_.load(std::memory_order_acquire);
975 size_t tasksPerRing = (count + ringCount - 1) / ringCount;
977 if (tasksPerRing <= 1) {
978 scheduleBulkToRingsFastPath(count, ringCount, std::forward<Generator>(gen), fallbackToken);
980 scheduleBulkToRingsBatched(
981 count, ringCount, tasksPerRing, std::forward<Generator>(gen), fallbackToken);
984 auto* ws = detail::consumeLoad(wakeState_);
985 if (enableEpochWaiter_.load(std::memory_order_acquire) && ws) {
986#if !defined(DISPENSO_DISABLE_CASCADE_WAKERANGE)
987 ws->cascadeWakeSeed(
static_cast<int32_t
>(count));
989 ws->wakeRange(
static_cast<int32_t
>(count));
998template <
typename Generator>
1000 using difference_type = std::ptrdiff_t;
1001 using value_type = OnceFunction;
1002 using pointer = OnceFunction*;
1003 using reference = OnceFunction&;
1004 using iterator_category = std::input_iterator_tag;
1008 OnceFunction operator*() {
1009 return (*gen)(index);
1011 BulkGenIter& operator++() {
1015 BulkGenIter operator++(
int) {
1016 BulkGenIter tmp = *
this;
1023template <
typename Generator>
1024void ThreadPool::scheduleBulkEnqueue(
1027 moodycamel::ProducerToken* token) {
1028 detail::BulkGenIter<typename std::remove_reference<Generator>::type> it{&gen, 0};
1031 workRemaining_.fetch_add(
static_cast<ssize_t
>(count), std::memory_order_release);
1033 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN();
1036 enqueued = work_.enqueue_bulk(*token, it, count);
1038 enqueued = work_.enqueue_bulk(it, count);
1040 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_END();
1041 if (DISPENSO_EXPECT(!enqueued,
false)) {
1042 workRemaining_.fetch_sub(
static_cast<ssize_t
>(count), std::memory_order_relaxed);
1043#if defined(__cpp_exceptions)
1044 throw std::bad_alloc();
1050 centralQueueNonEmpty_.store(
true, std::memory_order_relaxed);
1056 auto* ws = detail::consumeLoad(wakeState_);
1057 if (enableEpochWaiter_.load(std::memory_order_acquire) && ws) {
1058 int32_t sleeping = ws->totalSleeping();
1060 int32_t notWorking = numNotWorking_.load(std::memory_order_relaxed);
1061 int32_t spinning = std::max(int32_t{0}, notWorking - sleeping);
1063 int32_t effectiveSpinners = std::max(int32_t{0}, spinning - kSpinnerWakeThreshold + 1);
1064 int32_t toWake = std::max(int32_t{0},
static_cast<int32_t
>(count) - effectiveSpinners);
1065 toWake = std::min(toWake, sleeping);
1066 if (toWake <= ws->branchFactor()) {
1068 for (int32_t i = 0; i < toWake; ++i) {
1069 if (ws->claimAndWakeOne() < 0) {
1078 ws->cascadeWakeSeed(toWake);
1084template <
bool kPlaced,
typename Generator>
1085void ThreadPool::scheduleBulkImpl(
size_t count, Generator&& gen) {
1090 ssize_t numPool = numThreads_.load(std::memory_order_relaxed);
1092 for (
size_t i = 0; i < count; ++i) {
1099 size_t chunkSize =
static_cast<size_t>(numPool) +
static_cast<size_t>(numPool) / 2;
1102 ssize_t curWork = workRemaining_.load(std::memory_order_relaxed);
1103 ssize_t loadFactor = poolLoadFactor_.load(std::memory_order_relaxed);
1104 if (curWork > loadFactor) {
1108 ssize_t room = loadFactor - curWork;
1109 size_t toEnqueue = std::min({count - i, chunkSize,
static_cast<size_t>(room)});
1110 if (toEnqueue == 0) {
1115 workRemaining_.fetch_add(
static_cast<ssize_t
>(toEnqueue), std::memory_order_release);
1116 for (
size_t j = 0; j < toEnqueue; ++j) {
1117 scheduleImplPlaced({gen(base + j)},
nullptr);
1120 scheduleBulkEnqueue(toEnqueue, [&gen, base](
size_t j) {
return gen(base + j); });
1127template <
typename Generator>
1129 scheduleBulkImpl<false>(count, std::forward<Generator>(gen));
1132template <
typename Generator>
1133void ThreadPool::scheduleBulkPlaced(
size_t count, Generator&& gen) {
1134 scheduleBulkImpl<true>(count, std::forward<Generator>(gen));
static constexpr size_type capacity() noexcept
Returns the maximum number of elements the buffer can hold.
AwakeRef(AwakeRef &&other) noexcept
Move-construct, transferring the keep-awake reference from other.
AwakeRef(ThreadPool *pool)
Acquire a keep-awake reference on pool, incrementing its keep-awake count.
void reset()
Release the held keep-awake reference, if any.
void setSignalingWake(bool enable, const std::chrono::duration< Rep, Period > &sleepDuration=std::chrono::microseconds(kDefaultSleepLenUs))
DISPENSO_DLL_ACCESS ~ThreadPool()
ssize_t numThreads() const
DISPENSO_DLL_ACCESS void resize(ssize_t n) DISPENSO_NO_THREAD_SAFETY_ANALYSIS
void scheduleBulk(size_t count, Generator &&gen)
DISPENSO_DLL_ACCESS ThreadPool(size_t n, size_t poolLoadMultiplier=32)
DISPENSO_DLL_ACCESS ThreadPool & globalThreadPool()
DISPENSO_DLL_ACCESS void resizeGlobalThreadPool(size_t numThreads)