Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

892 lines
28 KiB
C++

//
// MIT License
// Copyright (c) 2020 Jonathan R. Madsen
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED
// "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ---------------------------------------------------------------
// Tasking class implementation
//
// Class Description:
//
// This file creates a class for an efficient thread-pool that
// accepts work in the form of tasks.
//
// ---------------------------------------------------------------
// Author: Jonathan Madsen (Feb 13th 2018)
// ---------------------------------------------------------------
#include "PTL/ThreadPool.hh"
#include "PTL/GetEnv.hh"
#include "PTL/ScopeDestructor.hh"
#include "PTL/ThreadData.hh"
#include "PTL/Threading.hh"
#include "PTL/UserTaskQueue.hh"
#include "PTL/VUserTaskQueue.hh"
#include <cassert>
#include <chrono>
#include <mutex>
#include <new>
#include <stdexcept>
#include <thread>
//======================================================================================//
namespace
{
PTL::ThreadData*&
thread_data()
{
return PTL::ThreadData::GetInstance();
}
} // namespace
namespace PTL
{
namespace
{
ThreadPool::thread_id_map_t*&
thread_id_map()
{
static auto* _v = new ThreadPool::thread_id_map_t{};
return _v;
}
auto ptl_init_thread_id_map = (thread_id_map(), true);
} // namespace
//======================================================================================//
ThreadPool::thread_id_map_t&
ThreadPool::f_thread_ids()
{
return *thread_id_map();
}
//======================================================================================//
ThreadPool::size_type&
ThreadPool::f_default_pool_size()
{
static size_type _v =
GetEnv<size_type>("PTL_NUM_THREADS", Thread::hardware_concurrency());
return _v;
}
//======================================================================================//
// static member function that calls the member function we want the thread to
// run
void
ThreadPool::start_thread(ThreadPool* tp, thread_data_t* _data, intmax_t _idx,
// NOLINTNEXTLINE(performance-unnecessary-value-param)
std::shared_ptr<std::promise<void>> _ready)
{
if(_ready)
_ready->set_value();
if(tp->get_verbose() > 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] Starting thread " << _idx << "..." << std::endl;
}
auto _thr_data = std::make_shared<ThreadData>(tp);
{
AutoLock lock(TypeMutex<ThreadPool>(), std::defer_lock);
if(!lock.owns_lock())
lock.lock();
if(_idx < 0)
_idx = f_thread_ids().size();
f_thread_ids()[std::this_thread::get_id()] = _idx;
SetThreadId(_idx);
_data->emplace_back(_thr_data);
}
thread_data() = _thr_data.get();
tp->record_entry();
tp->execute_thread(thread_data()->current_queue);
tp->record_exit();
if(tp->get_verbose() > 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] Thread " << _idx << " terminating..."
<< std::endl;
}
}
//======================================================================================//
const ThreadPool::thread_id_map_t&
ThreadPool::get_thread_ids()
{
return f_thread_ids();
}
//======================================================================================//
uintmax_t
ThreadPool::get_thread_id(ThreadId _tid)
{
uintmax_t _idx = 0;
{
AutoLock lock(TypeMutex<ThreadPool>(), std::defer_lock);
if(!lock.owns_lock())
lock.lock();
auto itr = f_thread_ids().find(_tid);
if(itr == f_thread_ids().end())
{
_idx = f_thread_ids().size();
f_thread_ids()[_tid] = _idx;
}
else
{
_idx = itr->second;
}
}
return _idx;
}
//======================================================================================//
uintmax_t
ThreadPool::get_this_thread_id()
{
return get_thread_id(ThisThread::get_id());
}
//======================================================================================//
uintmax_t
ThreadPool::add_thread_id(ThreadId _tid)
{
AutoLock lock(TypeMutex<ThreadPool>(), std::defer_lock);
if(!lock.owns_lock())
lock.lock();
if(f_thread_ids().find(_tid) == f_thread_ids().end())
{
auto _idx = f_thread_ids().size();
f_thread_ids()[_tid] = _idx;
SetThreadId(_idx);
}
return f_thread_ids().at(_tid);
}
//======================================================================================//
bool
ThreadPool::remove_thread_id(ThreadId _tid)
{
AutoLock lock(TypeMutex<ThreadPool>(), std::defer_lock);
if(!lock.owns_lock())
lock.lock();
if(auto itr = f_thread_ids().find(_tid); itr != f_thread_ids().end())
{
f_thread_ids().erase(itr);
return true;
}
return false;
}
//======================================================================================//
ThreadPool::ThreadPool(const Config& _cfg)
: m_use_affinity{ _cfg.use_affinity }
, m_tbb_tp{ _cfg.use_tbb }
, m_verbose{ _cfg.verbose }
, m_priority{ _cfg.priority }
, m_pool_state{ std::make_shared<std::atomic_short>(thread_pool::state::NONINIT) }
, m_task_queue{ _cfg.task_queue }
, m_init_func{ _cfg.initializer }
, m_fini_func{ _cfg.finalizer }
, m_affinity_func{ _cfg.set_affinity }
{
auto master_id = get_this_thread_id();
if(master_id != 0 && m_verbose > 1)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] ThreadPool created on worker thread" << std::endl;
}
if(!thread_data())
thread_data() = new ThreadData(this);
static thread_local ScopeDestructor _cleanup_thread_data{ []() {
delete thread_data();
thread_data() = nullptr;
} };
// initialize after get_this_thread_id so master is zero
if(_cfg.init)
this->initialize_threadpool(_cfg.pool_size);
}
//======================================================================================//
ThreadPool::~ThreadPool()
{
if(m_alive_flag->load())
{
std::cerr << "Warning! ThreadPool was not properly destroyed! Call "
"destroy_threadpool() before deleting the ThreadPool object to "
"eliminate this message."
<< std::endl;
m_pool_state->store(thread_pool::state::STOPPED);
m_task_lock->lock();
m_task_cond->notify_all();
m_task_lock->unlock();
for(auto& itr : m_threads)
itr.join();
m_threads.clear();
}
// delete owned resources
if(m_delete_task_queue)
{
delete m_task_queue;
}
delete m_tbb_task_arena;
delete m_tbb_task_group;
}
//======================================================================================//
bool
ThreadPool::is_initialized() const
{
return !(m_pool_state->load() == thread_pool::state::NONINIT);
}
//======================================================================================//
void
ThreadPool::record_entry()
{
++(*m_thread_active);
}
//======================================================================================//
void
ThreadPool::record_exit()
{
--(*m_thread_active);
}
//======================================================================================//
void
ThreadPool::set_affinity(intmax_t i, Thread& _thread) const
{
try
{
NativeThread native_thread = _thread.native_handle();
intmax_t _pin = m_affinity_func(i);
if(m_verbose > 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] Setting pin affinity for thread "
<< get_thread_id(_thread.get_id()) << " to " << _pin << std::endl;
}
SetPinAffinity(_pin, native_thread);
} catch(std::runtime_error& e)
{
std::cerr << "[PTL::ThreadPool] Error setting pin affinity: " << e.what()
<< std::endl;
}
}
//======================================================================================//
void
ThreadPool::set_priority(int _prio, Thread& _thread) const
{
try
{
NativeThread native_thread = _thread.native_handle();
if(m_verbose > 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] Setting thread "
<< get_thread_id(_thread.get_id()) << " priority to " << _prio
<< std::endl;
}
SetThreadPriority(_prio, native_thread);
} catch(std::runtime_error& e)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] Error setting thread priority: " << e.what()
<< std::endl;
}
}
//======================================================================================//
ThreadPool::size_type
ThreadPool::initialize_threadpool(size_type proposed_size)
{
//--------------------------------------------------------------------//
// return before initializing
if(proposed_size < 1)
return 0;
//--------------------------------------------------------------------//
// store that has been started
if(!m_alive_flag->load())
m_pool_state->store(thread_pool::state::STARTED);
#if defined(PTL_USE_TBB)
//--------------------------------------------------------------------//
// handle tbb task scheduler
if(m_tbb_tp)
{
m_tbb_tp = true;
m_pool_size = proposed_size;
tbb_global_control_t*& _global_control = tbb_global_control();
// delete if wrong size
if(m_pool_size != proposed_size)
{
delete _global_control;
_global_control = nullptr;
}
if(!_global_control)
{
_global_control = new tbb_global_control_t(
tbb::global_control::max_allowed_parallelism, proposed_size + 1);
if(m_verbose > 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] ThreadPool [TBB] initialized with "
<< m_pool_size << " threads." << std::endl;
}
}
// create task group (used for async)
if(!m_tbb_task_group)
{
m_tbb_task_group = new tbb_task_group_t{};
execute_on_all_threads([this]() { m_init_func(); });
}
return m_pool_size;
}
#endif
m_alive_flag->store(true);
//--------------------------------------------------------------------//
// if started, stop some thread if smaller or return if equal
if(m_pool_state->load() == thread_pool::state::STARTED)
{
if(m_pool_size > proposed_size)
{
while(stop_thread() > proposed_size)
;
if(m_verbose > 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] ThreadPool initialized with "
<< m_pool_size << " threads." << std::endl;
}
if(!m_task_queue)
{
m_delete_task_queue = true;
m_task_queue = new UserTaskQueue(m_pool_size);
}
else
{
m_task_queue->resize(m_pool_size);
}
return m_pool_size;
}
else if(m_pool_size == proposed_size) // NOLINT
{
if(m_verbose > 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "ThreadPool initialized with " << m_pool_size << " threads."
<< std::endl;
}
if(!m_task_queue)
{
m_delete_task_queue = true;
m_task_queue = new UserTaskQueue(m_pool_size);
}
return m_pool_size;
}
}
//--------------------------------------------------------------------//
// reserve enough space to prevent realloc later
{
AutoLock _task_lock(*m_task_lock);
m_is_joined.reserve(proposed_size);
}
if(!m_task_queue)
{
m_delete_task_queue = true;
m_task_queue = new UserTaskQueue(proposed_size);
}
auto this_tid = get_this_thread_id();
auto _ready = std::vector<std::shared_ptr<std::promise<void>>>{};
for(size_type i = m_pool_size; i < proposed_size; ++i)
{
// add the threads
try
{
// promise notifying that thread has started
auto _ready_v = _ready.emplace_back(std::make_shared<std::promise<void>>());
// create thread
auto thr = Thread{ ThreadPool::start_thread, this, &m_thread_data,
this_tid + i + 1, _ready_v };
// only reaches here if successful creation of thread
++m_pool_size;
// store thread
m_main_threads.push_back(thr.get_id());
// list of joined thread booleans
m_is_joined.push_back(false);
// set the affinity
if(m_use_affinity)
set_affinity(i, thr);
set_priority(m_priority, thr);
// store
m_threads.emplace_back(std::move(thr));
} catch(std::runtime_error& e)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] " << e.what()
<< std::endl; // issue creating thread
continue;
} catch(std::bad_alloc& e)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] " << e.what() << std::endl;
continue;
}
}
// wait up to one second total
for(auto& itr : _ready)
itr->get_future().wait_for(std::chrono::seconds{ 1 } / _ready.size());
//------------------------------------------------------------------------//
AutoLock _task_lock(*m_task_lock);
// thread pool size doesn't match with join vector
// this will screw up joining later
if(m_is_joined.size() != m_main_threads.size())
{
std::stringstream ss;
ss << "ThreadPool::initialize_threadpool - boolean is_joined vector "
<< "is a different size than threads vector: " << m_is_joined.size() << " vs. "
<< m_main_threads.size() << " (tid: " << std::this_thread::get_id() << ")";
throw std::runtime_error(ss.str());
}
if(m_verbose > 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] ThreadPool initialized with " << m_pool_size
<< " threads." << std::endl;
}
return m_main_threads.size();
}
//======================================================================================//
ThreadPool::size_type
ThreadPool::destroy_threadpool()
{
// Note: this is not for synchronization, its for thread communication!
// destroy_threadpool() will only be called from the main thread, yet
// the modified m_pool_state may not show up to other threads until its
// modified in a lock!
//------------------------------------------------------------------------//
m_pool_state->store(thread_pool::state::STOPPED);
//--------------------------------------------------------------------//
// handle tbb task scheduler
#if defined(PTL_USE_TBB)
if(m_tbb_task_group)
{
execute_on_all_threads([this]() { m_fini_func(); });
auto _func = [&]() { m_tbb_task_group->wait(); };
if(m_tbb_task_arena)
m_tbb_task_arena->execute(_func);
else
_func();
delete m_tbb_task_group;
m_tbb_task_group = nullptr;
}
if(m_tbb_task_arena)
{
delete m_tbb_task_arena;
m_tbb_task_arena = nullptr;
}
if(m_tbb_tp && tbb_global_control())
{
tbb_global_control_t*& _global_control = tbb_global_control();
delete _global_control;
_global_control = nullptr;
m_tbb_tp = false;
AutoLock lock(TypeMutex<decltype(std::cerr)>());
if(m_verbose > 0)
{
std::cerr << "[PTL::ThreadPool] ThreadPool [TBB] destroyed" << std::endl;
}
}
#endif
if(!m_alive_flag->load())
return 0;
//------------------------------------------------------------------------//
// notify all threads we are shutting down
m_task_lock->lock();
m_task_cond->notify_all();
m_task_lock->unlock();
//------------------------------------------------------------------------//
if(m_is_joined.size() != m_main_threads.size())
{
std::stringstream ss;
ss << " ThreadPool::destroy_thread_pool - boolean is_joined vector "
<< "is a different size than threads vector: " << m_is_joined.size() << " vs. "
<< m_main_threads.size() << " (tid: " << std::this_thread::get_id() << ")";
throw std::runtime_error(ss.str());
}
for(size_type i = 0; i < m_is_joined.size(); i++)
{
//--------------------------------------------------------------------//
//
if(i < m_threads.size())
m_threads.at(i).join();
//--------------------------------------------------------------------//
// if its joined already, nothing else needs to be done
if(m_is_joined.at(i))
continue;
//--------------------------------------------------------------------//
// join
if(std::this_thread::get_id() == m_main_threads[i])
continue;
//--------------------------------------------------------------------//
// thread id and index
auto _tid = m_main_threads[i];
//--------------------------------------------------------------------//
// erase thread from thread ID list
remove_thread_id(_tid);
//--------------------------------------------------------------------//
// it's joined
m_is_joined.at(i) = true;
}
m_thread_data.clear();
m_threads.clear();
m_main_threads.clear();
m_is_joined.clear();
m_alive_flag->store(false);
auto start = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration<double>{};
// wait maximum of 30 seconds for threads to exit
while(m_thread_active->load() > 0 && elapsed.count() < 30)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
elapsed = std::chrono::steady_clock::now() - start;
}
auto _active = m_thread_active->load();
if(get_verbose() > 0)
{
if(_active == 0)
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] ThreadPool destroyed" << std::endl;
}
else
{
AutoLock lock(TypeMutex<decltype(std::cerr)>());
std::cerr << "[PTL::ThreadPool] ThreadPool destroyed but " << _active
<< " threads might still be active (and cause a termination error)"
<< std::endl;
}
}
if(m_delete_task_queue)
{
delete m_task_queue;
m_task_queue = nullptr;
}
return 0;
}
//======================================================================================//
ThreadPool::size_type
ThreadPool::stop_thread()
{
if(!m_alive_flag->load() || m_pool_size == 0)
return 0;
m_pool_state->store(thread_pool::state::PARTIAL);
//------------------------------------------------------------------------//
// notify all threads we are shutting down
m_task_lock->lock();
m_is_stopped.push_back(true);
m_task_cond->notify_one();
m_task_lock->unlock();
//------------------------------------------------------------------------//
while(!m_is_stopped.empty() && m_stop_threads.empty())
;
// lock up the task queue
AutoLock _task_lock(*m_task_lock);
while(!m_stop_threads.empty())
{
auto tid = m_stop_threads.front();
// remove from stopped
m_stop_threads.pop_front();
// remove from main
for(auto itr = m_main_threads.begin(); itr != m_main_threads.end(); ++itr)
{
if(*itr == tid)
{
m_main_threads.erase(itr);
break;
}
}
// remove from join list
m_is_joined.pop_back();
}
m_pool_state->store(thread_pool::state::STARTED);
m_pool_size = m_main_threads.size();
return m_main_threads.size();
}
//======================================================================================//
ThreadPool::task_queue_t*&
ThreadPool::get_valid_queue(task_queue_t*& _queue) const
{
return get_valid_queue(_queue, nullptr);
}
//======================================================================================//
ThreadPool::task_queue_t*&
ThreadPool::get_valid_queue(task_queue_t*& _queue, bool* _delete_task_queue) const
{
if(!_queue)
{
if(_delete_task_queue)
*(_delete_task_queue) = true;
_queue = new UserTaskQueue{ static_cast<intmax_t>(m_pool_size) };
}
return _queue;
}
//======================================================================================//
void
ThreadPool::execute_thread(VUserTaskQueue* _task_queue)
{
++(*m_thread_awake);
// initialization function
m_init_func();
// finalization function (executed when scope is destroyed)
ScopeDestructor _fini{ [this]() { m_fini_func(); } };
ThreadId tid = ThisThread::get_id();
ThreadData* data = thread_data();
// auto thread_bin = _task_queue->GetThreadBin();
// auto workers = _task_queue->workers();
auto start = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration<double>{};
// check for updates for 60 seconds max
while(!_task_queue && elapsed.count() < 60)
{
elapsed = std::chrono::steady_clock::now() - start;
data->update();
_task_queue = data->current_queue;
}
if(!_task_queue)
{
--(*m_thread_awake);
throw std::runtime_error("No task queue was found after 60 seconds!");
}
assert(data->current_queue != nullptr);
assert(_task_queue == data->current_queue);
// essentially a dummy run
if(_task_queue)
{
data->within_task = true;
auto _task = _task_queue->GetTask();
if(_task)
{
(*_task)();
}
data->within_task = false;
}
// threads stay in this loop forever until thread-pool destroyed
while(true)
{
static thread_local auto p_task_lock = m_task_lock;
//--------------------------------------------------------------------//
// Try to pick a task
AutoLock _task_lock(*p_task_lock, std::defer_lock);
//--------------------------------------------------------------------//
auto leave_pool = [&]() {
auto _state = [&]() { return static_cast<int>(m_pool_state->load()); };
auto _pool_state = _state();
if(_pool_state > 0)
{
// stop whole pool
if(_pool_state == thread_pool::state::STOPPED)
{
if(_task_lock.owns_lock())
_task_lock.unlock();
return true;
}
// single thread stoppage
else if(_pool_state == thread_pool::state::PARTIAL) // NOLINT
{
if(!_task_lock.owns_lock())
_task_lock.lock();
if(!m_is_stopped.empty() && m_is_stopped.back())
{
m_stop_threads.push_back(tid);
m_is_stopped.pop_back();
if(_task_lock.owns_lock())
_task_lock.unlock();
// exit entire function
return true;
}
if(_task_lock.owns_lock())
_task_lock.unlock();
}
}
return false;
};
// We need to put condition.wait() in a loop for two reasons:
// 1. There can be spurious wake-ups (due to signal/ENITR)
// 2. When mutex is released for waiting, another thread can be woken up
// from a signal/broadcast and that thread can mess up the condition.
// So when the current thread wakes up the condition may no longer be
// actually true!
while(_task_queue->empty())
{
auto _state = [&]() { return static_cast<int>(m_pool_state->load()); };
auto _size = [&]() { return _task_queue->true_size(); };
auto _empty = [&]() { return _task_queue->empty(); };
auto _wake = [&]() { return (!_empty() || _size() > 0 || _state() > 0); };
if(leave_pool())
return;
if(_task_queue->true_size() == 0)
{
if(m_thread_awake->load() > 0)
--(*m_thread_awake);
// lock before sleeping on condition
if(!_task_lock.owns_lock())
_task_lock.lock();
// Wait until there is a task in the queue
// Unlocks mutex while waiting, then locks it back when signaled
// use lambda to control waking
m_task_cond->wait(_task_lock, _wake);
if(_state() == thread_pool::state::STOPPED)
return;
// unlock if owned
if(_task_lock.owns_lock())
_task_lock.unlock();
// notify that is awake
if(m_thread_awake->load() < m_pool_size)
++(*m_thread_awake);
}
else
break;
}
// release the lock
if(_task_lock.owns_lock())
_task_lock.unlock();
//----------------------------------------------------------------//
// leave pool if conditions dictate it
if(leave_pool())
return;
// activate guard against recursive deadlock
data->within_task = true;
//----------------------------------------------------------------//
// execute the task(s)
while(!_task_queue->empty())
{
auto _task = _task_queue->GetTask();
if(_task)
{
(*_task)();
}
}
//----------------------------------------------------------------//
// disable guard against recursive deadlock
data->within_task = false;
//----------------------------------------------------------------//
}
}
//======================================================================================//
} // namespace PTL