diff --git a/js/src/moz.build b/js/src/moz.build index f54ec3bcdce0..1daa3fce5d19 100755 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -395,6 +395,7 @@ UNIFIED_SOURCES += [ "vm/HelperThreads.cpp", "vm/Id.cpp", "vm/Initialization.cpp", + "vm/InternalThreadPool.cpp", "vm/Iteration.cpp", "vm/JitActivation.cpp", "vm/JSAtom.cpp", diff --git a/js/src/vm/HelperThreadState.h b/js/src/vm/HelperThreadState.h index f7c98e5479b0..c82680a85dec 100644 --- a/js/src/vm/HelperThreadState.h +++ b/js/src/vm/HelperThreadState.h @@ -99,8 +99,6 @@ class GlobalHelperThreadState { typedef Vector PromiseHelperTaskVector; typedef Vector ContextVector; - using HelperThreadVector = - Vector, 0, SystemAllocPolicy>; // Count of running task by each threadType. mozilla::EnumeratedArray @@ -113,9 +111,6 @@ class GlobalHelperThreadState { private: // The lists below are all protected by |lock|. - // List of available helper threads. - HelperThreadVector threads_; - // Ion compilation worklist and finished jobs. IonCompileTaskVector ionWorklist_, ionFinishedList_; IonFreeTaskVector ionFreeList_; @@ -197,7 +192,7 @@ class GlobalHelperThreadState { [[nodiscard]] bool ensureInitialized(); [[nodiscard]] bool ensureThreadCount(size_t count, - const AutoLockHelperThreadState& lock); + AutoLockHelperThreadState& lock); void finish(AutoLockHelperThreadState& lock); void finishThreads(AutoLockHelperThreadState& lock); @@ -240,14 +235,6 @@ class GlobalHelperThreadState { } private: - HelperThreadVector& threads(const AutoLockHelperThreadState& lock) { - return threads_; - } - const HelperThreadVector& threads( - const AutoLockHelperThreadState& lock) const { - return threads_; - } - void notifyOne(CondVar which, const AutoLockHelperThreadState&); public: @@ -488,43 +475,6 @@ static inline GlobalHelperThreadState& HelperThreadState() { return *gHelperThreadState; } -/* Individual helper thread, one allocated per core. */ -class HelperThread { - Thread thread; - - /* - * The profiling thread for this helper thread, which can be used to push - * and pop label frames. - * This field being non-null indicates that this thread has been registered - * and needs to be unregistered at shutdown. - */ - ProfilingStack* profilingStack = nullptr; - - public: - HelperThread(); - [[nodiscard]] bool init(); - - ThreadId threadId() { return thread.get_id(); } - - void join(); - - static void ThreadMain(void* arg); - void threadLoop(); - - void ensureRegisteredWithProfiler(); - void unregisterWithProfilerIfNeeded(); - - private: - struct AutoProfilerLabel { - AutoProfilerLabel(HelperThread* helperThread, const char* label, - JS::ProfilingCategoryPair categoryPair); - ~AutoProfilerLabel(); - - private: - ProfilingStack* profilingStack; - }; -}; - class MOZ_RAII AutoSetHelperThreadContext { JSContext* cx; AutoLockHelperThreadState& lock; diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 2fa7ad112d7d..098de7a53d6c 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -30,6 +30,7 @@ #include "util/NativeStack.h" #include "vm/ErrorReporting.h" #include "vm/HelperThreadState.h" +#include "vm/InternalThreadPool.h" #include "vm/MutexIDs.h" #include "vm/SharedImmutableStringsCache.h" #include "vm/Time.h" @@ -62,16 +63,6 @@ GlobalHelperThreadState* gHelperThreadState = nullptr; } // namespace js -// These macros are identical in function to the same-named ones in -// GeckoProfiler.h, but they are defined separately because SpiderMonkey can't -// use GeckoProfiler.h. -#define PROFILER_RAII_PASTE(id, line) id##line -#define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line) -#define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__) -#define AUTO_PROFILER_LABEL(label, categoryPair) \ - HelperThread::AutoProfilerLabel PROFILER_RAII( \ - this, label, JS::ProfilingCategoryPair::categoryPair) - bool js::CreateHelperThreadsState() { MOZ_ASSERT(!gHelperThreadState); gHelperThreadState = js_new(); @@ -117,7 +108,7 @@ bool js::SetFakeCPUCount(size_t count) { } void GlobalHelperThreadState::setCpuCount(size_t count) { - // This must be called before the threads have been initialized. + // This must be called before any threads have been initialized. AutoLockHelperThreadState lock; MOZ_ASSERT(!isInitialized(lock)); @@ -499,32 +490,6 @@ struct MOZ_RAII AutoSetContextParse { ~AutoSetContextParse() { TlsContext.get()->setParseTask(nullptr); } }; -// We want our default stack size limit to be approximately 2MB, to be safe, but -// expect most threads to use much less. On Linux, however, requesting a stack -// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it -// on first access, which we do not want. To avoid this possibility, we subtract -// 2 standard VM page sizes from our default. -static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096; -static const uint32_t kDefaultHelperStackQuota = 1800 * 1024; - -// TSan enforces a minimum stack size that's just slightly larger than our -// default helper stack size. It does this to store blobs of TSan-specific -// data on each thread's stack. Unfortunately, that means that even though -// we'll actually receive a larger stack than we requested, the effective -// usable space of that stack is significantly less than what we expect. -// To offset TSan stealing our stack space from underneath us, double the -// default. -// -// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't -// require all the thread-specific state that TSan does. -#if defined(MOZ_TSAN) -static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize; -static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota; -#else -static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize; -static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota; -#endif - AutoSetHelperThreadContext::AutoSetHelperThreadContext( AutoLockHelperThreadState& lock) : lock(lock) { @@ -1335,6 +1300,11 @@ bool GlobalHelperThreadState::ensureInitialized() { i = 0; } + if (useInternalThreadPool(lock) && + !InternalThreadPool::Initialize(threadCount, lock)) { + return false; + } + if (!ensureThreadCount(threadCount, lock)) { finishThreads(lock); return false; @@ -1346,7 +1316,7 @@ bool GlobalHelperThreadState::ensureInitialized() { } bool GlobalHelperThreadState::ensureThreadCount( - size_t count, const AutoLockHelperThreadState& lock) { + size_t count, AutoLockHelperThreadState& lock) { if (!ensureContextList(count, lock)) { return false; } @@ -1355,26 +1325,15 @@ bool GlobalHelperThreadState::ensureThreadCount( return false; } - if (!useInternalThreadPool(lock) || threads(lock).length() >= count) { - return true; - } + if (useInternalThreadPool(lock)) { + InternalThreadPool& pool = InternalThreadPool::Get(); + if (pool.threadCount(lock) < count) { + if (!pool.ensureThreadCount(count, lock)) { + return false; + } - if (!threads(lock).reserve(count)) { - return false; - } - - // Update threadCount on exit so this stays consistent with how many threads - // there are. - auto updateThreadCount = - mozilla::MakeScopeExit([&] { threadCount = threads(lock).length(); }); - - while (threads(lock).length() < count) { - auto thread = js::MakeUnique(); - if (!thread || !thread->init()) { - return false; + threadCount = pool.threadCount(lock); } - - threads(lock).infallibleEmplaceBack(std::move(thread)); } return true; @@ -1418,23 +1377,11 @@ void GlobalHelperThreadState::finish(AutoLockHelperThreadState& lock) { } void GlobalHelperThreadState::finishThreads(AutoLockHelperThreadState& lock) { - HelperThreadVector oldThreads; - waitForAllTasksLocked(lock); - terminating_ = true; - if (!useInternalThreadPool(lock)) { - return; - } - - notifyAll(GlobalHelperThreadState::PRODUCER, lock); - - std::swap(threads_, oldThreads); - - AutoUnlockHelperThreadState unlock(lock); - for (auto& thread : oldThreads) { - thread->join(); + if (InternalThreadPool::IsInitialized()) { + InternalThreadPool::ShutDown(lock); } } @@ -1477,7 +1424,7 @@ void GlobalHelperThreadState::assertIsLockedByCurrentThread() const { void GlobalHelperThreadState::dispatch( const AutoLockHelperThreadState& locked) { if (useInternalThreadPool(locked)) { - notifyOne(PRODUCER, locked); + InternalThreadPool::Get().dispatchTask(locked); return; } @@ -1635,7 +1582,10 @@ void GlobalHelperThreadState::addSizeOfIncludingThis( htStats.stateData += mallocSizeOf(this); - htStats.stateData += threads(lock).sizeOfExcludingThis(mallocSizeOf); + if (InternalThreadPool::IsInitialized()) { + htStats.stateData += + InternalThreadPool::Get().sizeOfIncludingThis(mallocSizeOf, lock); + } // Report memory used by various containers htStats.stateData += @@ -2460,56 +2410,6 @@ void GlobalHelperThreadState::mergeParseTaskRealm(JSContext* cx, gc::MergeRealms(parseTask->parseGlobal->as().realm(), dest); } -HelperThread::HelperThread() - : thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)) {} - -bool HelperThread::init() { - return thread.init(HelperThread::ThreadMain, this); -} - -void HelperThread::join() { thread.join(); } - -void HelperThread::ensureRegisteredWithProfiler() { - if (profilingStack) { - return; - } - - // Note: To avoid dead locks, we should not hold on the helper thread lock - // while calling this function. This is safe because the registerThread field - // is a WriteOnceData<> type stored on the global helper tread state. - JS::RegisterThreadCallback callback = HelperThreadState().registerThread; - if (callback) { - profilingStack = - callback("JS Helper", reinterpret_cast(GetNativeStackBase())); - } -} - -void HelperThread::unregisterWithProfilerIfNeeded() { - if (!profilingStack) { - return; - } - - // Note: To avoid dead locks, we should not hold on the helper thread lock - // while calling this function. This is safe because the unregisterThread - // field is a WriteOnceData<> type stored on the global helper tread state. - JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread; - if (callback) { - callback(); - profilingStack = nullptr; - } -} - -/* static */ -void HelperThread::ThreadMain(void* arg) { - ThisThread::SetName("JS Helper"); - - auto helper = static_cast(arg); - - helper->ensureRegisteredWithProfiler(); - helper->threadLoop(); - helper->unregisterWithProfilerIfNeeded(); -} - bool JSContext::addPendingCompileError(js::CompileError** error) { auto errorPtr = make_unique(); if (!errorPtr) { @@ -2781,45 +2681,6 @@ bool GlobalHelperThreadState::canStartTasks( canStartWasmTier2GeneratorTask(lock); } -HelperThread::AutoProfilerLabel::AutoProfilerLabel( - HelperThread* helperThread, const char* label, - JS::ProfilingCategoryPair categoryPair) - : profilingStack(helperThread->profilingStack) { - if (profilingStack) { - profilingStack->pushLabelFrame(label, nullptr, this, categoryPair); - } -} - -HelperThread::AutoProfilerLabel::~AutoProfilerLabel() { - if (profilingStack) { - profilingStack->pop(); - } -} - -void HelperThread::threadLoop() { - MOZ_ASSERT(CanUseExtraThreads()); - - AutoLockHelperThreadState lock; - - while (!HelperThreadState().isTerminating(lock)) { - // The selectors may depend on the HelperThreadState not changing - // between task selection and task execution, in particular, on new - // tasks not being added (because of the lifo structure of the work - // lists). Unlocking the HelperThreadState between task selection and - // execution is not well-defined. - - HelperThreadTask* task = HelperThreadState().findHighestPriorityTask(lock); - if (!task) { - AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE); - HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER); - continue; - } - - HelperThreadState().runTaskLocked(task, lock); - HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, lock); - } -} - void JS::RunHelperThreadTask() { MOZ_ASSERT(CanUseExtraThreads()); diff --git a/js/src/vm/InternalThreadPool.cpp b/js/src/vm/InternalThreadPool.cpp new file mode 100644 index 000000000000..643ab47a6bea --- /dev/null +++ b/js/src/vm/InternalThreadPool.cpp @@ -0,0 +1,259 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/InternalThreadPool.h" + +#include "mozilla/TimeStamp.h" + +#include "js/ProfilingCategory.h" +#include "js/ProfilingStack.h" +#include "threading/Thread.h" +#include "util/NativeStack.h" +#include "vm/HelperThreadState.h" + +// These macros are identical in function to the same-named ones in +// GeckoProfiler.h, but they are defined separately because SpiderMonkey can't +// use GeckoProfiler.h. +#define PROFILER_RAII_PASTE(id, line) id##line +#define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line) +#define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__) +#define AUTO_PROFILER_LABEL(label, categoryPair) \ + HelperThread::AutoProfilerLabel PROFILER_RAII( \ + this, label, JS::ProfilingCategoryPair::categoryPair) + +using namespace js; + +namespace js { + +class HelperThread { + Thread thread; + + /* + * The profiling thread for this helper thread, which can be used to push + * and pop label frames. + * This field being non-null indicates that this thread has been registered + * and needs to be unregistered at shutdown. + */ + ProfilingStack* profilingStack = nullptr; + + public: + HelperThread(); + [[nodiscard]] bool init(InternalThreadPool* pool); + + ThreadId threadId() { return thread.get_id(); } + + void join(); + + static void ThreadMain(InternalThreadPool* pool, HelperThread* helper); + void threadLoop(InternalThreadPool* pool); + + void ensureRegisteredWithProfiler(); + void unregisterWithProfilerIfNeeded(); + + private: + struct AutoProfilerLabel { + AutoProfilerLabel(HelperThread* helperThread, const char* label, + JS::ProfilingCategoryPair categoryPair); + ~AutoProfilerLabel(); + + private: + ProfilingStack* profilingStack; + }; +}; + +} // namespace js + +InternalThreadPool* InternalThreadPool::Instance = nullptr; + +/* static */ InternalThreadPool& InternalThreadPool::Get() { + MOZ_ASSERT(IsInitialized()); + return *Instance; +} + +/* static */ +bool InternalThreadPool::Initialize(size_t threadCount, + AutoLockHelperThreadState& lock) { + if (IsInitialized()) { + return true; + } + + auto instance = MakeUnique(); + if (!instance) { + return false; + } + + if (!instance->ensureThreadCount(threadCount, lock)) { + instance->shutDown(lock); + return false; + } + + Instance = instance.release(); + return true; +} + +bool InternalThreadPool::ensureThreadCount(size_t threadCount, + AutoLockHelperThreadState& lock) { + MOZ_ASSERT(threads(lock).length() < threadCount); + + if (!threads(lock).reserve(threadCount)) { + return false; + } + + while (threads(lock).length() < threadCount) { + auto thread = js::MakeUnique(); + if (!thread || !thread->init(this)) { + return false; + } + + threads(lock).infallibleEmplaceBack(std::move(thread)); + } + + return true; +} + +size_t InternalThreadPool::threadCount(const AutoLockHelperThreadState& lock) { + return threads(lock).length(); +} + +/* static */ +void InternalThreadPool::ShutDown(AutoLockHelperThreadState& lock) { + MOZ_ASSERT(HelperThreadState().isTerminating(lock)); + + Get().shutDown(lock); + js_delete(Instance); + Instance = nullptr; +} + +void InternalThreadPool::shutDown(AutoLockHelperThreadState& lock) { + MOZ_ASSERT(!terminating); + terminating = true; + + notifyAll(lock); + + for (auto& thread : threads(lock)) { + AutoUnlockHelperThreadState unlock(lock); + thread->join(); + } +} + +inline HelperThreadVector& InternalThreadPool::threads( + const AutoLockHelperThreadState& lock) { + return threads_.ref(); +} +inline const HelperThreadVector& InternalThreadPool::threads( + const AutoLockHelperThreadState& lock) const { + return threads_.ref(); +} + +size_t InternalThreadPool::sizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf, + const AutoLockHelperThreadState& lock) const { + return sizeof(InternalThreadPool) + + threads(lock).sizeOfExcludingThis(mallocSizeOf); +} + +void InternalThreadPool::dispatchTask(const AutoLockHelperThreadState& lock) { + notifyOne(lock); +} + +void InternalThreadPool::notifyOne(const AutoLockHelperThreadState& lock) { + wakeup.notify_one(); +} + +void InternalThreadPool::notifyAll(const AutoLockHelperThreadState& lock) { + wakeup.notify_all(); +} + +void InternalThreadPool::wait(AutoLockHelperThreadState& lock) { + wakeup.wait_for(lock, mozilla::TimeDuration::Forever()); +} + +HelperThread::HelperThread() + : thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)) {} + +bool HelperThread::init(InternalThreadPool* pool) { + return thread.init(HelperThread::ThreadMain, pool, this); +} + +void HelperThread::join() { thread.join(); } + +/* static */ +void HelperThread::ThreadMain(InternalThreadPool* pool, HelperThread* helper) { + ThisThread::SetName("JS Helper"); + + helper->ensureRegisteredWithProfiler(); + helper->threadLoop(pool); + helper->unregisterWithProfilerIfNeeded(); +} + +void HelperThread::ensureRegisteredWithProfiler() { + if (profilingStack) { + return; + } + + // Note: To avoid dead locks, we should not hold on the helper thread lock + // while calling this function. This is safe because the registerThread field + // is a WriteOnceData<> type stored on the global helper tread state. + JS::RegisterThreadCallback callback = HelperThreadState().registerThread; + if (callback) { + profilingStack = + callback("JS Helper", reinterpret_cast(GetNativeStackBase())); + } +} + +void HelperThread::unregisterWithProfilerIfNeeded() { + if (!profilingStack) { + return; + } + + // Note: To avoid dead locks, we should not hold on the helper thread lock + // while calling this function. This is safe because the unregisterThread + // field is a WriteOnceData<> type stored on the global helper tread state. + JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread; + if (callback) { + callback(); + profilingStack = nullptr; + } +} + +HelperThread::AutoProfilerLabel::AutoProfilerLabel( + HelperThread* helperThread, const char* label, + JS::ProfilingCategoryPair categoryPair) + : profilingStack(helperThread->profilingStack) { + if (profilingStack) { + profilingStack->pushLabelFrame(label, nullptr, this, categoryPair); + } +} + +HelperThread::AutoProfilerLabel::~AutoProfilerLabel() { + if (profilingStack) { + profilingStack->pop(); + } +} + +void HelperThread::threadLoop(InternalThreadPool* pool) { + MOZ_ASSERT(CanUseExtraThreads()); + + AutoLockHelperThreadState lock; + + while (!pool->terminating) { + // The selectors may depend on the HelperThreadState not changing + // between task selection and task execution, in particular, on new + // tasks not being added (because of the lifo structure of the work + // lists). Unlocking the HelperThreadState between task selection and + // execution is not well-defined. + + HelperThreadTask* task = HelperThreadState().findHighestPriorityTask(lock); + if (!task) { + AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE); + pool->wait(lock); + continue; + } + + HelperThreadState().runTaskLocked(task, lock); + HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, lock); + } +} diff --git a/js/src/vm/InternalThreadPool.h b/js/src/vm/InternalThreadPool.h new file mode 100644 index 000000000000..252f77324bab --- /dev/null +++ b/js/src/vm/InternalThreadPool.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * An internal thread pool, used for the shell and when + * JS::SetHelperThreadTaskCallback not called. + */ + +#ifndef vm_InternalThreadPool_h +#define vm_InternalThreadPool_h + +#include "js/AllocPolicy.h" +#include "js/UniquePtr.h" +#include "js/Vector.h" +#include "threading/ConditionVariable.h" +#include "threading/ProtectedData.h" + +// We want our default stack size limit to be approximately 2MB, to be safe, but +// expect most threads to use much less. On Linux, however, requesting a stack +// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it +// on first access, which we do not want. To avoid this possibility, we subtract +// 2 standard VM page sizes from our default. +static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096; +static const uint32_t kDefaultHelperStackQuota = 1800 * 1024; + +// TSan enforces a minimum stack size that's just slightly larger than our +// default helper stack size. It does this to store blobs of TSan-specific +// data on each thread's stack. Unfortunately, that means that even though +// we'll actually receive a larger stack than we requested, the effective +// usable space of that stack is significantly less than what we expect. +// To offset TSan stealing our stack space from underneath us, double the +// default. +// +// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't +// require all the thread-specific state that TSan does. +#if defined(MOZ_TSAN) +static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize; +static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota; +#else +static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize; +static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota; +#endif + +namespace js { + +class AutoLockHelperThreadState; +class HelperThread; + +using HelperThreadVector = + Vector, 0, SystemAllocPolicy>; + +class InternalThreadPool { + public: + static bool Initialize(size_t threadCount, AutoLockHelperThreadState& lock); + static void ShutDown(AutoLockHelperThreadState& lock); + + static bool IsInitialized() { return Instance; } + static InternalThreadPool& Get(); + + bool ensureThreadCount(size_t threadCount, AutoLockHelperThreadState& lock); + size_t threadCount(const AutoLockHelperThreadState& lock); + + void dispatchTask(const AutoLockHelperThreadState& lock); + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, + const AutoLockHelperThreadState& lock) const; + + private: + void shutDown(AutoLockHelperThreadState& lock); + + HelperThreadVector& threads(const AutoLockHelperThreadState& lock); + const HelperThreadVector& threads( + const AutoLockHelperThreadState& lock) const; + + void notifyOne(const AutoLockHelperThreadState& lock); + void notifyAll(const AutoLockHelperThreadState& lock); + void wait(AutoLockHelperThreadState& lock); + friend class HelperThread; + + static InternalThreadPool* Instance; + + HelperThreadLockData threads_; + + js::ConditionVariable wakeup; + + HelperThreadLockData terminating; +}; + +} // namespace js + +#endif /* vm_InternalThreadPool_h */