Import Geant4 10.7.0.beta source tree

This commit is contained in:
Gabriele Cosmo
2020-06-26 10:23:25 +02:00
parent c02c370437
commit 67ba86d073
1871 changed files with 174422 additions and 131884 deletions
+40
View File
@@ -0,0 +1,40 @@
//
// 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
//
//
#include "PTL/TaskAllocator.hh"
#include "PTL/TaskAllocatorList.hh"
using namespace PTL;
//======================================================================================//
TaskAllocatorBase::TaskAllocatorBase()
{
TaskAllocatorList::GetAllocatorList()->Register(this);
}
//======================================================================================//
TaskAllocatorBase::~TaskAllocatorBase() {}
//======================================================================================//
+129
View File
@@ -0,0 +1,129 @@
//
// 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
//
#include <iomanip>
#include "PTL/TaskAllocator.hh"
#include "PTL/TaskAllocatorList.hh"
using namespace PTL;
//======================================================================================//
TaskAllocatorList*&
TaskAllocatorList::fAllocatorList()
{
static thread_local TaskAllocatorList* _instance = nullptr;
return _instance;
}
//======================================================================================//
TaskAllocatorList*
TaskAllocatorList::GetAllocatorList()
{
if(!fAllocatorList())
{
fAllocatorList() = new TaskAllocatorList;
}
return fAllocatorList();
}
//======================================================================================//
TaskAllocatorList*
TaskAllocatorList::GetAllocatorListIfExist()
{
return fAllocatorList();
}
//======================================================================================//
TaskAllocatorList::TaskAllocatorList() {}
//======================================================================================//
TaskAllocatorList::~TaskAllocatorList() { fAllocatorList() = nullptr; }
//======================================================================================//
void
TaskAllocatorList::Register(TaskAllocatorBase* alloc)
{
fList.push_back(alloc);
}
//======================================================================================//
void
TaskAllocatorList::Destroy(int nStat, int verboseLevel)
{
int i = 0, j = 0;
double tmem = 0;
if(verboseLevel > 0)
{
std::cout << "================== Deleting memory pools ==================="
<< std::endl;
}
for(auto& itr : fList)
{
double mem = itr->GetAllocatedSize();
if(i < nStat)
{
i++;
tmem += mem;
itr->ResetStorage();
continue;
}
j++;
tmem += mem;
if(verboseLevel > 1)
{
std::cout << "Pool ID '" << itr->GetPoolType()
<< "', size : " << std::setprecision(3) << mem / 1048576
<< std::setprecision(6) << " MB" << std::endl;
}
itr->ResetStorage();
// delete *itr;
}
if(verboseLevel > 0)
{
std::cout << "Number of memory pools allocated: " << Size()
<< "; of which, static: " << i << std::endl;
std::cout << "Dynamic pools deleted: " << j
<< " / Total memory freed: " << std::setprecision(2) << tmem / 1048576
<< std::setprecision(6) << " MB" << std::endl;
std::cout << "============================================================"
<< std::endl;
}
fList.clear();
}
//======================================================================================//
int
TaskAllocatorList::Size() const
{
return fList.size();
}
//======================================================================================//
+124
View File
@@ -0,0 +1,124 @@
//
// 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
//
// TaskAllocatorPool
//
// Implementation file
//
// Author: G.Cosmo, November 2000
//
#include "PTL/TaskAllocatorPool.hh"
using namespace PTL;
// ************************************************************
// TaskAllocatorPool constructor
// ************************************************************
//
TaskAllocatorPool::TaskAllocatorPool(unsigned int sz)
: esize((sz < sizeof(PoolLink)) ? sizeof(PoolLink) : sz)
, csize((sz < 1024 / 2 - 16) ? (1024 - 16) : (sz * 10 - 16))
, chunks(nullptr)
, head(nullptr)
, nchunks(0)
{}
// ************************************************************
// TaskAllocatorPool copy constructor
// ************************************************************
//
TaskAllocatorPool::TaskAllocatorPool(const TaskAllocatorPool& right)
: esize(right.esize)
, csize(right.csize)
, chunks(right.chunks)
, head(right.head)
, nchunks(right.nchunks)
{}
// ************************************************************
// TaskAllocatorPool operator=
// ************************************************************
//
TaskAllocatorPool&
TaskAllocatorPool::operator=(const TaskAllocatorPool& right)
{
if(&right == this)
return *this;
chunks = right.chunks;
head = right.head;
nchunks = right.nchunks;
return *this;
}
// ************************************************************
// TaskAllocatorPool destructor
// ************************************************************
//
TaskAllocatorPool::~TaskAllocatorPool() { Reset(); }
// ************************************************************
// Reset
// ************************************************************
//
void
TaskAllocatorPool::Reset()
{
// Free all chunks
//
PoolChunk* n = chunks;
PoolChunk* p = nullptr;
while(n)
{
p = n;
n = n->next;
delete p;
}
head = nullptr;
chunks = nullptr;
nchunks = 0;
}
// ************************************************************
// Grow
// ************************************************************
//
void
TaskAllocatorPool::Grow()
{
// Allocate new chunk, organize it as a linked list of
// elements of size 'esize'
//
PoolChunk* n = new PoolChunk(csize);
n->next = chunks;
chunks = n;
nchunks++;
const int nelem = csize / esize;
char* start = n->mem;
char* last = &start[(nelem - 1) * esize];
for(char* p = start; p < last; p += esize)
{
reinterpret_cast<PoolLink*>(p)->next = reinterpret_cast<PoolLink*>(p + esize);
}
reinterpret_cast<PoolLink*>(last)->next = nullptr;
head = reinterpret_cast<PoolLink*>(start);
}
+152
View File
@@ -0,0 +1,152 @@
//
// 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
#include "PTL/TaskRunManager.hh"
#include "PTL/AutoLock.hh"
#include "PTL/Task.hh"
#include "PTL/TaskGroup.hh"
#include "PTL/TaskManager.hh"
#include "PTL/ThreadPool.hh"
#include "PTL/Threading.hh"
#include "PTL/Utility.hh"
#include <cstdlib>
#include <cstring>
#include <iterator>
using namespace PTL;
//======================================================================================//
TaskRunManager::pointer&
TaskRunManager::GetPrivateMasterRunManager(bool init, bool useTBB)
{
static pointer _instance = (init) ? new TaskRunManager(useTBB) : nullptr;
return _instance;
}
//======================================================================================//
TaskRunManager*
TaskRunManager::GetMasterRunManager(bool useTBB)
{
static pointer& _instance = GetPrivateMasterRunManager(true, useTBB);
return _instance;
}
//======================================================================================//
TaskRunManager*
TaskRunManager::GetInstance(bool useTBB)
{
return GetMasterRunManager(useTBB);
}
//======================================================================================//
TaskRunManager::TaskRunManager(bool useTBB)
: m_is_initialized(false)
, m_verbose(0)
, m_workers(std::thread::hardware_concurrency())
, m_task_queue(nullptr)
, m_thread_pool(nullptr)
, m_task_manager(nullptr)
{
if(!GetPrivateMasterRunManager(false))
{
GetPrivateMasterRunManager(false) = this;
}
#ifdef PTL_USE_TBB
auto _useTBB = GetEnv<bool>("FORCE_TBB", useTBB);
if(_useTBB)
useTBB = true;
#endif
// handle TBB
ThreadPool::set_use_tbb(useTBB);
m_workers = GetEnv<uint64_t>("PTL_NUM_THREADS", m_workers);
}
//======================================================================================//
TaskRunManager::~TaskRunManager() {}
//======================================================================================//
void
TaskRunManager::Initialize(uint64_t n)
{
m_workers = n;
// create threadpool if needed + task manager
if(!m_thread_pool)
{
if(m_verbose > 0)
std::cout << "TaskRunManager :: Creating thread pool..." << std::endl;
m_thread_pool = new ThreadPool(m_workers, m_task_queue);
if(m_verbose > 0)
std::cout << "TaskRunManager :: Creating task manager..." << std::endl;
m_task_manager = new TaskManager(m_thread_pool);
}
// or resize
else if(m_workers != m_thread_pool->size())
{
if(m_verbose > 0)
{
std::cout << "TaskRunManager :: Resizing thread pool from "
<< m_thread_pool->size() << " to " << m_workers << " threads ..."
<< std::endl;
}
m_thread_pool->resize(m_workers);
}
// create the joiners
if(ThreadPool::using_tbb())
{
if(m_verbose > 0)
std::cout << "TaskRunManager :: Using TBB..." << std::endl;
}
else
{
if(m_verbose > 0)
std::cout << "TaskRunManager :: Using ThreadPool..." << std::endl;
}
m_is_initialized = true;
if(m_verbose > 0)
std::cout << "TaskRunManager :: initialized..." << std::endl;
}
//======================================================================================//
void
TaskRunManager::Terminate()
{
m_is_initialized = false;
m_thread_pool->destroy_threadpool();
delete m_task_manager;
delete m_thread_pool;
m_task_manager = nullptr;
m_thread_pool = nullptr;
}
//======================================================================================//
+65
View File
@@ -0,0 +1,65 @@
//
// 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
//
#include "PTL/ThreadData.hh"
#include "PTL/ThreadPool.hh"
#include "PTL/Threading.hh"
#include "PTL/VUserTaskQueue.hh"
#include <iostream>
using namespace PTL;
//======================================================================================//
ThreadData*&
ThreadData::GetInstance()
{
static thread_local ThreadData* _instance = nullptr;
return _instance;
}
//======================================================================================//
ThreadData::ThreadData(ThreadPool* tp)
: is_master(tp->is_master())
, within_task(false)
, task_depth(0)
, thread_pool(tp)
, current_queue(tp->get_queue())
, queue_stack({ current_queue })
{}
//======================================================================================//
void
ThreadData::update()
{
current_queue = thread_pool->get_queue();
queue_stack.push_back(current_queue);
}
//======================================================================================//
ThreadData::~ThreadData() {}
//======================================================================================//
+759
View File
@@ -0,0 +1,759 @@
//
// 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/Globals.hh"
#include "PTL/ThreadData.hh"
#include "PTL/UserTaskQueue.hh"
#include "PTL/VUserTaskQueue.hh"
#include <cstdlib>
using namespace PTL;
//======================================================================================//
inline intmax_t
ncores()
{
return static_cast<intmax_t>(Thread::hardware_concurrency());
}
//======================================================================================//
ThreadPool::thread_id_map_t ThreadPool::f_thread_ids;
//======================================================================================//
namespace
{
ThreadData*&
thread_data()
{
return ThreadData::GetInstance();
}
} // namespace
//======================================================================================//
bool ThreadPool::f_use_tbb = false;
//======================================================================================//
// static member function that calls the member function we want the thread to
// run
void
ThreadPool::start_thread(ThreadPool* tp, intmax_t _idx)
{
{
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;
}
static thread_local std::unique_ptr<ThreadData> _unique_data(new ThreadData(tp));
thread_data() = _unique_data.get();
tp->record_entry();
tp->execute_thread(thread_data()->current_queue);
tp->record_exit();
}
//======================================================================================//
// static member function that checks enabling of tbb library
bool
ThreadPool::using_tbb()
{
return f_use_tbb;
}
//======================================================================================//
// static member function that initialized tbb library
void
ThreadPool::set_use_tbb(bool enable)
{
#if defined(PTL_USE_TBB)
f_use_tbb = enable;
#else
ConsumeParameters<bool>(enable);
#endif
}
//======================================================================================//
const ThreadPool::thread_id_map_t&
ThreadPool::GetThreadIDs()
{
return f_thread_ids;
}
//======================================================================================//
uintmax_t
ThreadPool::GetThisThreadID()
{
auto _tid = ThisThread::get_id();
{
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;
}
}
return f_thread_ids[_tid];
}
//======================================================================================//
ThreadPool::ThreadPool(const size_type& pool_size, VUserTaskQueue* task_queue,
bool _use_affinity, const affinity_func_t& _affinity_func)
: m_use_affinity(_use_affinity)
, m_tbb_tp(false)
, m_verbose(0)
, m_pool_size(0)
, m_master_tid(ThisThread::get_id())
, m_alive_flag(std::make_shared<std::atomic_bool>(false))
, m_pool_state(std::make_shared<std::atomic_short>(thread_pool::state::NONINIT))
, m_thread_awake(std::make_shared<std::atomic_uintmax_t>(0))
, m_task_lock(std::make_shared<Mutex>())
, m_task_cond(std::make_shared<Condition>())
, m_task_queue(task_queue)
, m_tbb_task_group(nullptr)
, m_init_func([]() { return; })
, m_affinity_func(_affinity_func)
{
m_verbose = GetEnv<int>("PTL_VERBOSE", m_verbose);
auto master_id = GetThisThreadID();
if(master_id != 0 && m_verbose > 1)
std::cerr << "ThreadPool created on non-master slave" << std::endl;
thread_data() = new ThreadData(this);
// initialize after GetThisThreadID so master is zero
this->initialize_threadpool(pool_size);
if(!m_task_queue)
m_task_queue = new UserTaskQueue(m_pool_size);
}
//======================================================================================//
ThreadPool::~ThreadPool()
{
//------------------------------------------------------------------------//
// set state to stopped
m_pool_state->store(thread_pool::state::STOPPED);
//------------------------------------------------------------------------//
// notify all threads we are shutting down
m_task_lock->lock();
CONDITIONBROADCAST(m_task_cond.get());
m_task_lock->unlock();
//--------------------------------------------------------------------//
// set to dead
m_alive_flag->store(false);
//--------------------------------------------------------------------//
// delete tbb task scheduler
#ifdef PTL_USE_TBB
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;
std::cout << "ThreadPool [TBB] destroyed" << std::endl;
}
//--------------------------------------------------------------------//
// delete tbb task-group
if(m_tbb_task_group)
{
m_tbb_task_group->wait();
delete m_tbb_task_group;
m_tbb_task_group = nullptr;
}
#endif
auto _tid = std::this_thread::get_id();
if(f_thread_ids.find(_tid) != f_thread_ids.end())
f_thread_ids.erase(f_thread_ids.find(_tid));
for(auto& itr : m_main_threads)
if(f_thread_ids.find(itr) != f_thread_ids.end())
f_thread_ids.erase(f_thread_ids.find(itr));
m_thread_awake.reset();
std::cout << "ThreadPool destroyed" << std::endl;
/*
// Release resources
if(m_pool_state.load() != thread_pool::state::STOPPED)
{
size_type ret = destroy_threadpool();
while(ret > 0)
ret = stop_thread();
}
// wait until thread pool is fully destroyed
while(is_alive())
;
// delete thread-local allocator and erase thread IDS
auto _tid = std::this_thread::get_id();
if(f_thread_ids.find(_tid) != f_thread_ids.end())
f_thread_ids.erase(f_thread_ids.find(_tid));
// deleted by ThreadData
// delete m_task_queue;
*/
}
//======================================================================================//
bool
ThreadPool::is_initialized() const
{
return !(m_pool_state->load() == thread_pool::state::NONINIT);
}
//======================================================================================//
void
ThreadPool::set_affinity(intmax_t i, Thread& _thread)
{
try
{
NativeThread native_thread = _thread.native_handle();
intmax_t _pin = m_affinity_func(i);
if(m_verbose > 0)
{
std::cout << "Setting pin affinity for thread " << _thread.get_id() << " to "
<< _pin << std::endl;
}
Threading::SetPinAffinity(_pin, native_thread);
} catch(std::runtime_error& e)
{
std::cout << "Error setting pin affinity" << std::endl;
std::cerr << e.what() << std::endl; // issue assigning affinity
}
}
//======================================================================================//
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);
//--------------------------------------------------------------------//
// handle tbb task scheduler
#ifdef PTL_USE_TBB
if(f_use_tbb)
{
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)
{
std::cout << "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();
return m_pool_size;
}
// NOLINT(readability-else-after-return)
if(f_use_tbb && tbb_global_control())
{
m_tbb_tp = false;
tbb_global_control_t*& _global_control = tbb_global_control();
if(_global_control)
{
delete _global_control;
_global_control = nullptr;
}
// delete task group (used for async)
if(m_tbb_task_group)
{
m_tbb_task_group->wait();
delete m_tbb_task_group;
m_tbb_task_group = nullptr;
}
}
#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)
{
std::cout << "ThreadPool initialized with " << m_pool_size << " threads."
<< std::endl;
}
if(!m_task_queue)
m_task_queue = new UserTaskQueue(m_pool_size);
return m_pool_size;
}
else if(m_pool_size == proposed_size) // NOLINT
{
if(m_verbose > 0)
{
std::cout << "ThreadPool initialized with " << m_pool_size << " threads."
<< std::endl;
}
if(!m_task_queue)
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);
}
auto this_tid = GetThisThreadID();
for(size_type i = m_pool_size; i < proposed_size; ++i)
{
// add the threads
try
{
Thread tid(ThreadPool::start_thread, this, this_tid + i + 1);
// only reaches here if successful creation of thread
++m_pool_size;
// store thread
m_main_threads.push_back(tid.get_id());
// list of joined thread booleans
m_is_joined.push_back(false);
// set the affinity
if(m_use_affinity)
set_affinity(i, tid);
// detach
tid.detach();
} catch(std::runtime_error& e)
{
std::cerr << e.what() << std::endl; // issue creating thread
continue;
} catch(std::bad_alloc& e)
{
std::cerr << e.what() << std::endl;
continue;
}
}
//------------------------------------------------------------------------//
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)
{
std::cout << "ThreadPool initialized with " << m_pool_size << " threads."
<< std::endl;
}
if(!m_task_queue)
m_task_queue = new UserTaskQueue(m_main_threads.size());
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
#ifdef PTL_USE_TBB
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;
std::cout << "ThreadPool [TBB] destroyed" << std::endl;
}
if(m_tbb_task_group)
{
m_tbb_task_group->wait();
delete m_tbb_task_group;
m_tbb_task_group = nullptr;
}
#endif
if(!m_alive_flag->load())
return 0;
//------------------------------------------------------------------------//
// notify all threads we are shutting down
m_task_lock->lock();
CONDITIONBROADCAST(m_task_cond.get());
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 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
if(f_thread_ids.find(_tid) != f_thread_ids.end())
f_thread_ids.erase(f_thread_ids.find(_tid));
//--------------------------------------------------------------------//
// it's joined
m_is_joined.at(i) = true;
//--------------------------------------------------------------------//
// try waking up a bunch of threads that are still waiting
CONDITIONBROADCAST(m_task_cond.get());
//--------------------------------------------------------------------//
}
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(_active == 0)
std::cout << "ThreadPool destroyed" << std::endl;
else
std::cout << "ThreadPool destroyed but " << _active
<< " threads might still be active (and cause a termination error)"
<< std::endl;
return 0;
}
//======================================================================================//
ThreadPool::size_type
ThreadPool::stop_thread()
{
if(!m_alive_flag->load() || m_pool_size == 0)
return 0;
//------------------------------------------------------------------------//
// notify all threads we are shutting down
m_task_lock->lock();
m_is_stopped.push_back(true);
CONDITIONNOTIFY(m_task_cond.get());
m_task_lock->unlock();
//------------------------------------------------------------------------//
// 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_size = m_main_threads.size();
return m_main_threads.size();
}
//======================================================================================//
void
ThreadPool::execute_thread(VUserTaskQueue* _task_queue)
{
// how long the thread waits on condition variable
// static int wait_time = GetEnv<int>("PTL_POOL_WAIT_TIME", 5);
++(*m_thread_awake);
// initialization function
m_init_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)();
if(!_task->group())
delete _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 && 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 && 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)();
if(!_task->group())
delete _task;
}
}
//----------------------------------------------------------------//
// disable guard against recursive deadlock
data->within_task = false;
//----------------------------------------------------------------//
}
}
//======================================================================================//
+127
View File
@@ -0,0 +1,127 @@
//
// 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
//
// Threading.cc
//
// ---------------------------------------------------------------
// Author: Andrea Dotti (15 Feb 2013): First Implementation
// ---------------------------------------------------------------
#include "PTL/Threading.hh"
#include "PTL/AutoLock.hh"
#include "PTL/Globals.hh"
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
# include <Windows.h>
#else
# include <sys/syscall.h>
# include <sys/types.h>
# include <unistd.h>
#endif
#include <atomic>
using namespace PTL;
//======================================================================================//
namespace
{
thread_local int ThreadID = Threading::MASTER_ID;
std::atomic_int numActThreads(0);
} // namespace
//======================================================================================//
Pid_t
Threading::GetPidId()
{
// In multithreaded mode return Thread ID
return std::this_thread::get_id();
}
//======================================================================================//
unsigned
Threading::GetNumberOfCores()
{
return std::thread::hardware_concurrency();
}
//======================================================================================//
void
Threading::SetThreadId(int value)
{
ThreadID = value;
}
int
Threading::GetThreadId()
{
return ThreadID;
}
bool
Threading::IsWorkerThread()
{
return (ThreadID >= 0);
}
bool
Threading::IsMasterThread()
{
return (ThreadID == MASTER_ID);
}
//======================================================================================//
bool
Threading::SetPinAffinity(int cpu, NativeThread& aT)
{
#if defined(__linux__) || defined(_AIX)
cpu_set_t* aset = new cpu_set_t;
CPU_ZERO(aset);
CPU_SET(cpu, aset);
pthread_t& _aT = static_cast<pthread_t&>(aT);
return (pthread_setaffinity_np(_aT, sizeof(cpu_set_t), aset) == 0);
#else // Not available for Mac, WIN,...
ConsumeParameters(cpu, aT);
return true;
#endif
}
//======================================================================================//
int
Threading::WorkerThreadLeavesPool()
{
return numActThreads--;
}
int
Threading::WorkerThreadJoinsPool()
{
return numActThreads++;
}
int
Threading::GetNumberOfRunningWorkerThreads()
{
return numActThreads.load();
}
//======================================================================================//
+472
View File
@@ -0,0 +1,472 @@
//
// 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:
// ---------------------------------------------------------------
// Author: Jonathan Madsen
// ---------------------------------------------------------------
#include "PTL/UserTaskQueue.hh"
#include "PTL/Task.hh"
#include "PTL/TaskGroup.hh"
#include "PTL/ThreadPool.hh"
#include <cassert>
using namespace PTL;
//======================================================================================//
UserTaskQueue::UserTaskQueue(intmax_t nworkers, UserTaskQueue* parent)
: VUserTaskQueue(nworkers)
, m_is_clone((parent) ? true : false)
, m_thread_bin((parent) ? (ThreadPool::GetThisThreadID() % (nworkers + 1)) : 0)
, m_insert_bin((parent) ? (ThreadPool::GetThisThreadID() % (nworkers + 1)) : 0)
, m_hold((parent) ? parent->m_hold : new std::atomic_bool(false))
, m_ntasks((parent) ? parent->m_ntasks : new std::atomic_uintmax_t(0))
, m_subqueues((parent) ? parent->m_subqueues : new TaskSubQueueContainer())
{
// create nthreads + 1 subqueues so there is always a subqueue available
if(!parent)
{
for(intmax_t i = 0; i < nworkers + 1; ++i)
m_subqueues->push_back(new TaskSubQueue(m_ntasks));
}
#if defined(DEBUG)
if(GetEnv<int>("PTL_VERBOSE", 0) > 3)
{
RecursiveAutoLock l(TypeRecursiveMutex<decltype(std::cout)>());
std::stringstream ss;
ss << ThreadPool::GetThisThreadID() << "> " << ThisThread::get_id() << " ["
<< __FUNCTION__ << ":" << __LINE__ << "] "
<< "this = " << this << ", "
<< "clone = " << std::boolalpha << m_is_clone << ", "
<< "thread = " << m_thread_bin << ", "
<< "insert = " << m_insert_bin << ", "
<< "hold = " << m_hold->load() << " @ " << m_hold << ", "
<< "tasks = " << m_ntasks->load() << " @ " << m_ntasks << ", "
<< "subqueue = " << m_subqueues << ", "
<< "size = " << true_size() << ", "
<< "empty = " << true_empty();
std::cout << ss.str() << std::endl;
}
#endif
}
//======================================================================================//
UserTaskQueue::~UserTaskQueue()
{
if(!m_is_clone)
{
for(auto& itr : *m_subqueues)
{
assert(itr->size() == 0);
delete itr;
}
m_subqueues->clear();
delete m_hold;
delete m_ntasks;
delete m_subqueues;
}
}
//======================================================================================//
void
UserTaskQueue::resize(intmax_t n)
{
AutoLock l(m_mutex);
if(m_workers < n)
{
while(m_workers < n)
{
m_subqueues->push_back(new TaskSubQueue(m_ntasks));
++m_workers;
}
}
else if(m_workers > n)
{
while(m_workers > n)
{
delete m_subqueues->back();
m_subqueues->pop_back();
--m_workers;
}
}
}
//======================================================================================//
VUserTaskQueue*
UserTaskQueue::clone()
{
return new UserTaskQueue(workers(), this);
}
//======================================================================================//
intmax_t
UserTaskQueue::GetThreadBin() const
{
// get a thread id number
static thread_local intmax_t tl_bin =
(m_thread_bin + ThreadPool::GetThisThreadID()) % (m_workers + 1);
return tl_bin;
}
//======================================================================================//
intmax_t
UserTaskQueue::GetInsertBin() const
{
return (++m_insert_bin % (m_workers + 1));
}
//======================================================================================//
UserTaskQueue::task_pointer
UserTaskQueue::GetThreadBinTask()
{
intmax_t tbin = GetThreadBin();
TaskSubQueue* task_subq = (*m_subqueues)[tbin % (m_workers + 1)];
task_pointer _task = nullptr;
//------------------------------------------------------------------------//
auto get_task = [&]() {
if(task_subq->AcquireClaim())
{
// run task
_task = task_subq->PopTask(true);
// release the claim on the bin
task_subq->ReleaseClaim();
}
if(_task)
--(*m_ntasks);
// return success if valid pointer
return (_task != nullptr);
};
//------------------------------------------------------------------------//
// while not empty
while(!task_subq->empty())
{
if(get_task())
break;
}
return _task;
}
//======================================================================================//
UserTaskQueue::task_pointer
UserTaskQueue::GetTask(intmax_t subq, intmax_t nitr)
{
// exit if empty
if(this->true_empty())
return nullptr;
// ensure the thread has a bin assignment
intmax_t tbin = GetThreadBin();
intmax_t n = (subq < 0) ? tbin : subq;
if(nitr < 1)
nitr = (m_workers + 1); // * m_ntasks->load(std::memory_order_relaxed);
if(m_hold->load(std::memory_order_relaxed))
{
return GetThreadBinTask();
}
task_pointer _task = nullptr;
//------------------------------------------------------------------------//
auto get_task = [&](intmax_t _n) {
TaskSubQueue* task_subq = (*m_subqueues)[_n % (m_workers + 1)];
// try to acquire a claim for the bin
// if acquired, no other threads will access bin until claim is released
if(!task_subq->empty() && task_subq->AcquireClaim())
{
// pop task out of bin
_task = task_subq->PopTask(n == tbin);
// release the claim on the bin
task_subq->ReleaseClaim();
}
if(_task)
--(*m_ntasks);
// return success if valid pointer
return (_task != nullptr);
};
//------------------------------------------------------------------------//
// there are num_workers+1 bins so there is always a bin that is open
// execute num_workers+2 iterations so the thread checks its bin twice
// while(!empty())
{
for(intmax_t i = 0; i < nitr; ++i, ++n)
{
if(get_task(n % (m_workers + 1)))
return _task;
}
}
// only reached if looped over all bins (and looked in own bin twice)
// and found no work so return an empty task and the thread will be put to
// sleep if there is still no work by the time it reaches its
// condition variable
return _task;
}
//======================================================================================//
intmax_t
UserTaskQueue::InsertTask(task_pointer task, ThreadData* data, intmax_t subq)
{
// skip increment here (handled externally)
++(*m_ntasks);
bool spin = m_hold->load(std::memory_order_relaxed);
intmax_t tbin = GetThreadBin();
if(data && data->within_task)
{
subq = tbin;
// spin = true;
}
// subq is -1 unless specified so unless specified
// GetInsertBin() call increments a counter and returns
// counter % (num_workers + 1) so that tasks are distributed evenly
// among the bins
intmax_t n = (subq < 0) ? GetInsertBin() : subq;
//------------------------------------------------------------------------//
auto insert_task = [&](intmax_t _n) {
TaskSubQueue* task_subq = (*m_subqueues)[_n];
// TaskSubQueue* next_subq = (*m_subqueues)[(_n + 1) % (m_workers + 1)];
// if not threads bin and size difference, insert into smaller
// if(n != tbin && next_subq->size() < task_subq->size())
// task_subq = next_subq;
// try to acquire a claim for the bin
// if acquired, no other threads will access bin until claim is released
if(task_subq->AcquireClaim())
{
// push the task into the bin
task_subq->PushTask(task);
// release the claim on the bin
task_subq->ReleaseClaim();
// return success
return true;
}
return false;
};
//------------------------------------------------------------------------//
// if not in "hold/spin mode", where thread only inserts tasks into
// specified bin, then move onto next bin
//
if(spin)
{
n = n % (m_workers + 1);
while(!insert_task(n))
;
return n;
}
// there are num_workers+1 bins so there is always a bin that is open
// execute num_workers+2 iterations so the thread checks its bin twice
while(true)
{
auto _n = (n++) % (m_workers + 1);
if(insert_task(_n))
return _n;
}
return GetThreadBin();
}
//======================================================================================//
void
UserTaskQueue::ExecuteOnAllThreads(ThreadPool* tp, function_type func)
{
typedef TaskGroup<int, int> task_group_type;
typedef std::map<int64_t, bool> thread_execute_map_t;
if(!tp->is_alive())
{
func();
return;
}
auto join_func = [=](int& ref, int i) {
ref += i;
return ref;
};
task_group_type* tg = new task_group_type(join_func, tp);
// wait for all threads to finish any work
// NOTE: will cause deadlock if called from a task
while(tp->get_active_threads_count() > 0)
ThisThread::sleep_for(std::chrono::milliseconds(10));
thread_execute_map_t* thread_execute_map = new thread_execute_map_t();
AcquireHold();
for(int i = 0; i < (m_workers + 1); ++i)
{
if(i == GetThreadBin())
continue;
//--------------------------------------------------------------------//
auto thread_specific_func = [&]() {
static Mutex _mtx;
_mtx.lock();
bool& _executed = (*thread_execute_map)[GetThreadBin()];
_mtx.unlock();
if(!_executed)
{
func();
_executed = true;
return 1;
}
return 0;
};
//--------------------------------------------------------------------//
auto _task = tg->wrap(thread_specific_func);
//++(*m_ntasks);
// TaskSubQueue* task_subq = (*m_subqueues)[i];
// task_subq->PushTask(_task);
InsertTask(_task, ThreadData::GetInstance(), i);
}
tp->notify_all();
int nexecuted = tg->join();
if(nexecuted != m_workers)
{
std::stringstream msg;
msg << "Failure executing routine on all threads! Only " << nexecuted
<< " threads executed function out of " << m_workers;
std::cerr << msg.str() << std::endl;
// Exception("UserTaskQueue::ExecuteOnAllThreads", "TaskQueue0000",
// JustWarning, msg);
}
delete thread_execute_map;
ReleaseHold();
}
//======================================================================================//
void
UserTaskQueue::ExecuteOnSpecificThreads(ThreadIdSet tid_set, ThreadPool* tp,
function_type func)
{
typedef TaskGroup<int, int> task_group_type;
typedef std::map<int64_t, bool> thread_execute_map_t;
auto join_func = [=](int& ref, int i) {
ref += i;
return ref;
};
task_group_type* tg = new task_group_type(join_func, tp);
// wait for all threads to finish any work
// NOTE: will cause deadlock if called from a task
while(tp->get_active_threads_count() > 0)
ThisThread::sleep_for(std::chrono::milliseconds(10));
if(!tp->is_alive())
{
func();
return;
}
thread_execute_map_t* thread_execute_map = new thread_execute_map_t();
//========================================================================//
// wrap the function so that it will only be executed if the thread
// has an ID in the set
auto thread_specific_func = [=]() {
static Mutex _mtx;
_mtx.lock();
bool& _executed = (*thread_execute_map)[GetThreadBin()];
_mtx.unlock();
if(!_executed && tid_set.count(ThisThread::get_id()) > 0)
{
func();
_executed = true;
return 1;
}
return 0;
};
//========================================================================//
if(tid_set.count(ThisThread::get_id()) > 0)
func();
AcquireHold();
for(int i = 0; i < (m_workers + 1); ++i)
{
if(i == GetThreadBin())
continue;
auto _task = tg->wrap(thread_specific_func);
InsertTask(_task, ThreadData::GetInstance(), i);
}
tp->notify_all();
int nexecuted = tg->join();
if(nexecuted != m_workers)
{
std::stringstream msg;
msg << "Failure executing routine on all threads! Only " << nexecuted
<< " threads executed function out of " << tid_set.size();
std::cerr << msg.str() << std::endl;
// Exception("UserTaskQueue::ExecuteOnSpecificThreads", "TaskQueue0001",
// JustWarning, msg);
}
delete thread_execute_map;
ReleaseHold();
}
//======================================================================================//
void
UserTaskQueue::AcquireHold()
{
bool _hold;
while(!(_hold = m_hold->load(std::memory_order_relaxed)))
{
m_hold->compare_exchange_strong(_hold, true, std::memory_order_release,
std::memory_order_relaxed);
}
}
//======================================================================================//
void
UserTaskQueue::ReleaseHold()
{
bool _hold;
while((_hold = m_hold->load(std::memory_order_relaxed)))
{
m_hold->compare_exchange_strong(_hold, false, std::memory_order_release,
std::memory_order_relaxed);
}
}
//======================================================================================//
+108
View File
@@ -0,0 +1,108 @@
//
// 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 an abstract base class for the thread-pool tasking
// system
//
// ---------------------------------------------------------------
// Author: Jonathan Madsen (Feb 13th 2018)
// ---------------------------------------------------------------
#include "PTL/VTask.hh"
#include "PTL/ThreadData.hh"
#include "PTL/ThreadPool.hh"
#include "PTL/VTaskGroup.hh"
using namespace PTL;
//======================================================================================//
VTask::VTask()
: m_depth(0)
, m_group(nullptr)
, m_pool(nullptr)
{}
//======================================================================================//
VTask::VTask(VTaskGroup* task_group)
: m_depth(0)
, m_group(task_group)
, m_pool((m_group) ? task_group->pool() : nullptr)
{}
//======================================================================================//
VTask::VTask(ThreadPool* tp)
: m_depth(0)
, m_group(nullptr)
, m_pool(tp)
{}
//======================================================================================//
VTask::~VTask() {}
//======================================================================================//
void
VTask::operator--()
{
if(m_group)
{
intmax_t _count = --(*m_group);
if(_count < 2)
{
try
{
m_group->task_cond()->notify_all();
} catch(std::system_error& e)
{
auto tid = ThreadPool::GetThisThreadID();
AutoLock l(TypeMutex<decltype(std::cerr)>(), std::defer_lock);
if(!l.owns_lock())
l.lock();
std::cerr << "[" << tid << "] Caught system error: " << e.what()
<< std::endl;
}
}
}
}
//======================================================================================//
bool
VTask::is_native_task() const
{
return (m_group) ? m_group->is_native_task_group() : false;
}
//======================================================================================//
ThreadPool*
VTask::pool() const
{
return (!m_pool && m_group) ? m_group->pool() : m_pool;
}
//======================================================================================//
+220
View File
@@ -0,0 +1,220 @@
//
// 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 an abstract base class for the grouping the thread-pool
// tasking system into independently joinable units
//
// ---------------------------------------------------------------
// Author: Jonathan Madsen (Feb 13th 2018)
// ---------------------------------------------------------------
#include "PTL/VTaskGroup.hh"
#include "PTL/Globals.hh"
#include "PTL/Task.hh"
#include "PTL/TaskRunManager.hh"
#include "PTL/ThreadData.hh"
#include "PTL/ThreadPool.hh"
#include "PTL/VTask.hh"
using namespace PTL;
//======================================================================================//
std::atomic_uintmax_t&
vtask_group_counter()
{
static std::atomic_uintmax_t _instance(0);
return _instance;
}
//======================================================================================//
int VTaskGroup::f_verbose = GetEnv<int>("PTL_VERBOSE", 0);
//======================================================================================//
VTaskGroup::VTaskGroup(ThreadPool* tp)
: m_id(vtask_group_counter()++)
, m_pool(tp)
, m_tot_task_count(std::make_shared<atomic_int>(0))
, m_task_cond(std::make_shared<condition_t>())
, m_task_lock(std::make_shared<lock_t>())
, m_main_tid(std::this_thread::get_id())
{
if(!m_pool && TaskRunManager::GetMasterRunManager())
m_pool = TaskRunManager::GetMasterRunManager()->GetThreadPool();
if(!m_pool)
{
std::cerr << __FUNCTION__ << "@" << __LINE__ << " :: Warning! "
<< "nullptr to thread pool!" << std::endl;
}
}
//======================================================================================//
VTaskGroup::~VTaskGroup() {}
//======================================================================================//
void
VTaskGroup::wait()
{
// if no pool was initially present at creation
if(!m_pool)
{
// check for master MT run-manager
if(TaskRunManager::GetMasterRunManager())
m_pool = TaskRunManager::GetMasterRunManager()->GetThreadPool();
// if MTRunManager does not exist or no thread pool created
if(!m_pool)
{
if(f_verbose > 0)
{
fprintf(stderr, "%s @ %i :: Warning! nullptr to thread-pool (%p)\n",
__FUNCTION__, __LINE__, static_cast<void*>(m_pool));
std::cerr << __FUNCTION__ << "@" << __LINE__ << " :: Warning! "
<< "nullptr to thread pool!" << std::endl;
}
return;
}
}
ThreadData* data = ThreadData::GetInstance();
if(!data)
return;
ThreadPool* tpool = (m_pool) ? m_pool : data->thread_pool;
VUserTaskQueue* taskq = (tpool) ? tpool->get_queue() : data->current_queue;
bool _is_master = data->is_master;
bool _within_task = data->within_task;
auto is_active_state = [&]() {
return (tpool->state()->load(std::memory_order_relaxed) !=
thread_pool::state::STOPPED);
};
auto execute_this_threads_tasks = [&]() {
if(!taskq)
return;
// only want to process if within a task
if((!_is_master || tpool->size() < 2) && _within_task)
{
int bin = static_cast<int>(taskq->GetThreadBin());
// const auto nitr = (tpool) ? tpool->size() : Thread::hardware_concurrency();
while(this->pending() > 0)
{
task_pointer _task = taskq->GetTask(bin);
if(_task)
(*_task)();
}
}
};
// checks for validity
if(!is_native_task_group())
{
// for external threads
if(!_is_master || tpool->size() < 2)
return;
}
else if(f_verbose > 0)
{
if(!tpool || !taskq)
{
// something is wrong, didn't create thread-pool?
fprintf(
stderr,
"%s @ %i :: Warning! nullptr to thread data (%p) or task-queue (%p)\n",
__FUNCTION__, __LINE__, static_cast<void*>(tpool),
static_cast<void*>(taskq));
}
// return if thread pool isn't built
else if(is_native_task_group() && !tpool->is_alive())
{
fprintf(stderr, "%s @ %i :: Warning! thread-pool is not alive!\n",
__FUNCTION__, __LINE__);
}
else if(!is_active_state())
{
fprintf(stderr, "%s @ %i :: Warning! thread-pool is not active!\n",
__FUNCTION__, __LINE__);
}
}
intmax_t wake_size = 2;
AutoLock _lock(*m_task_lock, std::defer_lock);
while(is_active_state())
{
execute_this_threads_tasks();
// while loop protects against spurious wake-ups
while(_is_master && pending() > 0 && is_active_state())
{
// auto _wake = [&]() { return (wake_size > pending() || !is_active_state());
// };
// lock before sleeping on condition
if(!_lock.owns_lock())
_lock.lock();
// Wait until signaled that a task has been competed
// Unlock mutex while wait, then lock it back when signaled
// when true, this wakes the thread
if(pending() >= wake_size)
{
m_task_cond->wait(_lock);
}
else
{
m_task_cond->wait_for(_lock, std::chrono::microseconds(100));
}
// unlock
if(_lock.owns_lock())
_lock.unlock();
}
// if pending is not greater than zero, we are joined
if(pending() <= 0)
break;
}
if(_lock.owns_lock())
_lock.unlock();
intmax_t ntask = this->task_count().load();
if(ntask > 0)
{
std::stringstream ss;
ss << "\nWarning! Join operation issue! " << ntask << " tasks still "
<< "are running!" << std::endl;
std::cerr << ss.str();
this->wait();
}
}
//======================================================================================//
+60
View File
@@ -0,0 +1,60 @@
//
// 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:
// Abstract base class for creating a task queue used by
// ThreadPool
// ---------------------------------------------------------------
// Author: Jonathan Madsen
// ---------------------------------------------------------------
#include "PTL/VUserTaskQueue.hh"
#include "PTL/TaskRunManager.hh"
using namespace PTL;
//======================================================================================//
VUserTaskQueue::VUserTaskQueue(intmax_t nworkers)
: m_workers(nworkers)
{
if(m_workers < 0)
{
TaskRunManager* rm = TaskRunManager::GetMasterRunManager();
m_workers = (rm) ? rm->GetNumberOfThreads() + 1 // number of threads + 1
: (2 * std::thread::hardware_concurrency()) + 1;
// hyperthreads + 1
}
}
//======================================================================================//
VUserTaskQueue::~VUserTaskQueue() {}
//======================================================================================//
/*
intmax_t& VUserTaskQueue::ThisThreadNumber() const
{
// get a thread id number
static thread_local intmax_t _tid;
return _tid;
}
*/
//======================================================================================//