flatten 20260225
This commit is contained in:
15
tjp/core/thread_pool/AutoThreadPool.h
Executable file
15
tjp/core/thread_pool/AutoThreadPool.h
Executable file
@@ -0,0 +1,15 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tjp/core/types/Types.h>
|
||||
|
||||
namespace tjp::core::thread_pool {
|
||||
|
||||
template<typename P>
|
||||
struct AutoThreadPool;
|
||||
|
||||
template<typename P, size_t minThreads_, size_t maxThreads_>
|
||||
struct AutoThreadPoolSized;
|
||||
|
||||
} // namespace
|
||||
151
tjp/core/thread_pool/AutoThreadPool.hpp
Executable file
151
tjp/core/thread_pool/AutoThreadPool.hpp
Executable file
@@ -0,0 +1,151 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AutoThreadPool.h"
|
||||
|
||||
#include <tjp/core/log/Log.h>
|
||||
#include <tjp/core/log/LogOf.h>
|
||||
#include <tjp/core/assert/debug_assert.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace tjp::core::thread_pool {
|
||||
|
||||
template<typename P>
|
||||
struct AutoThreadPool
|
||||
{
|
||||
using Pool = P;
|
||||
|
||||
Pool pool;
|
||||
size_t minThreads, maxThreads;
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> from;
|
||||
float averagingDuration;
|
||||
float averageDesiredThreads = 0;
|
||||
bool ending = false;
|
||||
|
||||
AutoThreadPool(size_t minThreads_, size_t maxThreads_, float averagingDuration_=1.0) :
|
||||
minThreads(minThreads_),
|
||||
maxThreads(maxThreads_),
|
||||
averagingDuration(averagingDuration_)
|
||||
{
|
||||
from = std::chrono::system_clock::now();
|
||||
averageDesiredThreads = minThreads;
|
||||
|
||||
pool.timeout = [&]() { onTimeOut(); };
|
||||
pool.resize(minThreads);
|
||||
}
|
||||
|
||||
~AutoThreadPool()
|
||||
{
|
||||
stop(true);
|
||||
}
|
||||
|
||||
void stop(bool wait)
|
||||
{
|
||||
ending = true;
|
||||
|
||||
pool.stop(wait);
|
||||
}
|
||||
|
||||
template<typename ...T>
|
||||
auto push(T && ...t)
|
||||
{
|
||||
return pool.with_lock([&]() {
|
||||
auto result = pool.push_noLock(std::forward<T>(t)...);
|
||||
onTimeOut();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
void onTimeOut()
|
||||
{
|
||||
if (ending)
|
||||
return;
|
||||
|
||||
auto to = std::chrono::system_clock::now();
|
||||
std::chrono::duration<float> duration_ = to - from;
|
||||
auto duration = duration_.count();
|
||||
|
||||
auto numRunning = pool.num_running();
|
||||
auto numIdle = pool.num_idle();
|
||||
auto numProcessing = numRunning - numIdle;
|
||||
auto numWaiting = pool.queue_size();
|
||||
auto numSlots = numProcessing + numWaiting;
|
||||
|
||||
if (numSlots > averageDesiredThreads)
|
||||
{
|
||||
averageDesiredThreads = numSlots;
|
||||
}
|
||||
else
|
||||
{
|
||||
averageDesiredThreads = (averagingDuration * averageDesiredThreads + duration * numSlots) / (averagingDuration + duration);
|
||||
debug_assert(averageDesiredThreads > 0);
|
||||
from = to;
|
||||
}
|
||||
|
||||
sLogDebug("core::autothreadpool", logOfThis(this) << logVar(averageDesiredThreads) << logVar(numProcessing) << logVar(numWaiting) << logVar(duration) << logVar(numRunning) << logVar(numIdle));
|
||||
|
||||
resize_no_lock();
|
||||
}
|
||||
|
||||
void resize ()
|
||||
{
|
||||
pool.with_lock([&]() {
|
||||
resize_no_lock();
|
||||
});
|
||||
}
|
||||
|
||||
void resize_no_lock ()
|
||||
{
|
||||
if (ending)
|
||||
return;
|
||||
|
||||
auto weight = 0.2;
|
||||
auto numDesiredThreadsIncrease = (int)std::round(averageDesiredThreads);
|
||||
auto numDesiredThreadsDecrease = (int)std::round(averageDesiredThreads + weight);
|
||||
if (!increase(numDesiredThreadsIncrease))
|
||||
cull(numDesiredThreadsDecrease);
|
||||
}
|
||||
|
||||
bool increase (size_t numDesiredThreads)
|
||||
{
|
||||
numDesiredThreads = std::min(maxThreads, numDesiredThreads);
|
||||
|
||||
auto numThreads = pool.num_requested();
|
||||
if (numThreads >= numDesiredThreads)
|
||||
return false;
|
||||
|
||||
sLogDebug("core::autothreadpool", logOfThis(this) << logVar(numThreads) << logVar(numDesiredThreads) << logVar(pool.num_running()));
|
||||
|
||||
pool.resize_no_lock(numDesiredThreads);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cull (size_t numDesiredThreads)
|
||||
{
|
||||
numDesiredThreads = std::max(minThreads, numDesiredThreads);
|
||||
|
||||
auto numThreads = pool.num_requested();
|
||||
if (numThreads <= numDesiredThreads)
|
||||
return false;
|
||||
|
||||
sLogDebug("core::autothreadpool", logOfThis(this) << logVar(numThreads) << logVar(numDesiredThreads) << logVar(pool.num_running()));
|
||||
|
||||
pool.resize_no_lock(numDesiredThreads);
|
||||
|
||||
return true;
|
||||
}
|
||||
} ;
|
||||
|
||||
template<typename P, size_t minThreads_, size_t maxThreads_>
|
||||
struct AutoThreadPoolSized : AutoThreadPool<P>
|
||||
{
|
||||
AutoThreadPoolSized () : AutoThreadPool<P>(minThreads_, maxThreads_) {}
|
||||
} ;
|
||||
|
||||
} // namespace
|
||||
287
tjp/core/thread_pool/Base.h
Executable file
287
tjp/core/thread_pool/Base.h
Executable file
@@ -0,0 +1,287 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tjp/core/threads/Lock.hpp>
|
||||
|
||||
#include <tjp/core/threads/Atomic.hpp>
|
||||
#include <tjp/core/containers/Function.hpp>
|
||||
#include <tjp/core/containers/Map.hpp>
|
||||
#include <tjp/core/containers/Optional.hpp>
|
||||
#include <tjp/core/types/Types.h>
|
||||
|
||||
#include <queue>
|
||||
|
||||
|
||||
namespace tjp::core::thread_pool {
|
||||
|
||||
using Mutex = core::Mutex;
|
||||
using ThreadID = u64;
|
||||
constexpr ThreadID ThreadID_Zero = 0;
|
||||
|
||||
template<typename T, typename P>
|
||||
class Base {
|
||||
|
||||
protected:
|
||||
using value_type = T;
|
||||
using self_type = P;
|
||||
using Size = int;
|
||||
|
||||
Atomic<Size> numRequested = 0;
|
||||
Atomic<Size> numRunning = 0;
|
||||
|
||||
Atomic<ThreadID> nextID = 1;
|
||||
|
||||
Map<ThreadID, std::unique_ptr<std::thread>> threads;
|
||||
Atomic<Size> numIdle = 0; // how many threads are waiting
|
||||
|
||||
Event::Mutex mutex;
|
||||
Event event;
|
||||
|
||||
protected:
|
||||
std::queue<T> q;
|
||||
|
||||
public:
|
||||
|
||||
Base()
|
||||
{
|
||||
}
|
||||
|
||||
Base(int nThreads)
|
||||
{
|
||||
resize(nThreads);
|
||||
}
|
||||
|
||||
~Base()
|
||||
{
|
||||
stop(true);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto with_lock(F &&f)
|
||||
{
|
||||
auto lock = lock_of(mutex);
|
||||
return f();
|
||||
}
|
||||
|
||||
// get the number of running threads in the pool
|
||||
int num_requested()
|
||||
{
|
||||
return numRequested;
|
||||
}
|
||||
|
||||
// number of idle threads
|
||||
int num_idle()
|
||||
{
|
||||
return numIdle;
|
||||
}
|
||||
|
||||
int num_running()
|
||||
{
|
||||
return numRunning;
|
||||
}
|
||||
|
||||
void resize(size_t nThreads)
|
||||
{
|
||||
auto lock = lock_of(mutex);
|
||||
resize_no_lock((Size)nThreads);
|
||||
}
|
||||
|
||||
void resize_no_lock(size_t nThreads)
|
||||
{
|
||||
numRequested = (Size)nThreads;
|
||||
on_resize();
|
||||
}
|
||||
|
||||
auto size()
|
||||
{
|
||||
return threads.size();
|
||||
}
|
||||
|
||||
auto queue_size()
|
||||
{
|
||||
return q.size();
|
||||
}
|
||||
|
||||
void on_resize()
|
||||
{
|
||||
while (numRequested > numRunning)
|
||||
{
|
||||
this->newThread(nextID++);
|
||||
}
|
||||
|
||||
if (numRequested < numRunning)
|
||||
{
|
||||
event.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void clear_queue()
|
||||
{
|
||||
q.clear();
|
||||
}
|
||||
|
||||
void stop(bool isWait = false)
|
||||
{
|
||||
resize(0);
|
||||
|
||||
if (isWait)
|
||||
{
|
||||
auto lock = lock_of(mutex);
|
||||
while (numRunning > 0)
|
||||
{
|
||||
event.wait(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto push_noLock(T &&t)
|
||||
{
|
||||
q.emplace(std::move(t));
|
||||
event.notify_one();
|
||||
|
||||
return q.size();
|
||||
}
|
||||
|
||||
auto push(T &&t)
|
||||
{
|
||||
auto lock = lock_of(mutex);
|
||||
return push_noLock(std::move(t));
|
||||
}
|
||||
|
||||
auto pop_noLock(Optional<T> &t)
|
||||
{
|
||||
debug_assert(!t.has_value());
|
||||
|
||||
if (q.empty())
|
||||
return false;
|
||||
|
||||
t = std::move(q.front());
|
||||
q.pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto pop(Optional<T> &t)
|
||||
{
|
||||
auto lock = lock_of(mutex);
|
||||
pop_noLock(t);
|
||||
}
|
||||
|
||||
public:
|
||||
Function<void()> timeout;
|
||||
|
||||
private:
|
||||
void onTimeOut()
|
||||
{
|
||||
if (timeout)
|
||||
timeout();
|
||||
}
|
||||
|
||||
// deleted
|
||||
Base(const Base &);// = delete;
|
||||
Base(Base &&);// = delete;
|
||||
Base & operator=(const Base &);// = delete;
|
||||
Base & operator=(Base &&);// = delete;
|
||||
|
||||
void onExit(ThreadID id)
|
||||
{
|
||||
sLogRelease("core::autothreadpool::delta", logOfThis(this) << " exit thread " << logVar(numRunning));
|
||||
|
||||
debug_assert(mutex.locked_by_caller());
|
||||
|
||||
auto thread_ = threads.find(id);
|
||||
debug_assert(thread_ != threads.end());
|
||||
|
||||
if (thread_ != threads.end())
|
||||
{
|
||||
auto &thread = thread_->second;
|
||||
thread->detach();
|
||||
threads.erase(thread_);
|
||||
}
|
||||
}
|
||||
|
||||
auto shouldExitThread ()
|
||||
{
|
||||
auto currentlyRunning = (Size)numRunning;
|
||||
while (currentlyRunning > numRequested)
|
||||
{
|
||||
auto oneLessRunning = currentlyRunning - 1;
|
||||
if (numRunning.compare_exchange_strong(currentlyRunning, oneLessRunning))
|
||||
{
|
||||
event.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
currentlyRunning = (Size)numRunning;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
void newThread(ThreadID id)
|
||||
{
|
||||
++numRunning;
|
||||
|
||||
sLogRelease("core::autothreadpool::delta", logOfThis(this) << " new thread " << logVar(numRunning));
|
||||
|
||||
auto f = [this, id]()
|
||||
{
|
||||
// this is only the duration to call timeout and possibly exit
|
||||
auto duration = std::chrono::milliseconds(100);
|
||||
|
||||
Optional<T> t;
|
||||
|
||||
auto lock = lock_of(mutex);
|
||||
pop_noLock(t);
|
||||
|
||||
while (true)
|
||||
{
|
||||
// process the next item
|
||||
while(t)
|
||||
{
|
||||
onTimeOut();
|
||||
|
||||
{
|
||||
auto unlock = unlock_of(mutex);
|
||||
static_cast<self_type *>(this)->process(id, std::move(*t));
|
||||
t.reset();
|
||||
}
|
||||
|
||||
if (shouldExitThread())
|
||||
return onExit(id);
|
||||
|
||||
pop_noLock(t);
|
||||
}
|
||||
|
||||
|
||||
// the queue is empty here, wait for the next command
|
||||
++numIdle;
|
||||
|
||||
// wait for an item
|
||||
// spurious wake ups are possible
|
||||
do
|
||||
{
|
||||
onTimeOut();
|
||||
|
||||
if (shouldExitThread())
|
||||
{
|
||||
--numIdle;
|
||||
return onExit(id);
|
||||
}
|
||||
|
||||
event.wait_for(lock, duration);
|
||||
pop_noLock(t);
|
||||
}
|
||||
while (!t);
|
||||
|
||||
--numIdle;
|
||||
}
|
||||
};
|
||||
|
||||
threads.emplace(id, new std::thread(f)); // compiler may not support std::make_unique()
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
86
tjp/core/thread_pool/FunctionalProcessor.hpp
Executable file
86
tjp/core/thread_pool/FunctionalProcessor.hpp
Executable file
@@ -0,0 +1,86 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Base.h"
|
||||
|
||||
#include <future>
|
||||
|
||||
namespace tjp::core::thread_pool {
|
||||
|
||||
class FunctionalProcessor : public Base<std::unique_ptr<Function<void(ThreadID)>>, FunctionalProcessor> {
|
||||
|
||||
public:
|
||||
|
||||
using Super = Base<std::unique_ptr<Function<void(ThreadID)>>, FunctionalProcessor>;
|
||||
|
||||
template<typename ... T>
|
||||
FunctionalProcessor(T && ...t) :
|
||||
Super(std::forward<T>(t)...)
|
||||
{
|
||||
}
|
||||
|
||||
~FunctionalProcessor()
|
||||
{
|
||||
stop(true);
|
||||
}
|
||||
|
||||
void process(ThreadID id, value_type &&f)
|
||||
{
|
||||
(*f)(id);
|
||||
}
|
||||
|
||||
template<typename F, typename... Rest>
|
||||
auto push_noLock(F && f, Rest&&... rest) ->std::future<decltype(f(ThreadID_Zero, rest...))>
|
||||
{
|
||||
auto pck = std::make_shared<
|
||||
std::packaged_task<decltype(f(ThreadID_Zero, rest...))(int)>
|
||||
>(
|
||||
std::bind(std::forward<F>(f), std::placeholders::_1, std::forward<Rest>(rest)...)
|
||||
);
|
||||
|
||||
auto _f = new std::function<void(ThreadID)>([pck](ThreadID id) {
|
||||
(*pck)(id);
|
||||
});
|
||||
|
||||
Super::push_noLock(value_type(_f));
|
||||
|
||||
return pck->get_future();
|
||||
}
|
||||
|
||||
template<typename F, typename... Rest>
|
||||
auto push(F && f, Rest&&... rest) ->std::future<decltype(f(ThreadID_Zero, rest...))>
|
||||
{
|
||||
auto lock = lock_of(mutex);
|
||||
|
||||
return push_noLock(std::forward<F>(f), std::forward<Rest>(rest)...);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto push_noLock(F && f) ->std::future<decltype(f(ThreadID_Zero))>
|
||||
{
|
||||
auto pck = std::make_shared<
|
||||
std::packaged_task<decltype(f(ThreadID_Zero))(ThreadID)>
|
||||
>(std::forward<F>(f));
|
||||
|
||||
auto _f = new std::function<void(ThreadID)>([pck](ThreadID id) {
|
||||
(*pck)(id);
|
||||
});
|
||||
|
||||
Super::push_noLock(value_type(_f));
|
||||
|
||||
return pck->get_future();
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto push(F && f) ->std::future<decltype(f(ThreadID_Zero))>
|
||||
{
|
||||
auto lock = lock_of(mutex);
|
||||
|
||||
return push_noLock(std::forward<F>(f));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
37
tjp/core/thread_pool/ObjectConsumer.hpp
Executable file
37
tjp/core/thread_pool/ObjectConsumer.hpp
Executable file
@@ -0,0 +1,37 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Base.h"
|
||||
|
||||
namespace tjp::core::thread_pool {
|
||||
|
||||
template<typename T>
|
||||
class ObjectConsumer : public Base<T, ObjectConsumer<T>> {
|
||||
|
||||
public:
|
||||
using Super = Base<T, ObjectConsumer<T>>;
|
||||
|
||||
Function<void(T &&)> f;
|
||||
|
||||
public:
|
||||
|
||||
template<typename ... U>
|
||||
ObjectConsumer(U && ...u) :
|
||||
Super(std::forward<U>(u)...)
|
||||
{
|
||||
}
|
||||
|
||||
~ObjectConsumer()
|
||||
{
|
||||
this->stop(true);
|
||||
}
|
||||
|
||||
void process(ThreadID id, typename Super::value_type &&t)
|
||||
{
|
||||
f(std::move(t));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
15
tjp/core/thread_pool/_delete/AutoThreadPool.h
Executable file
15
tjp/core/thread_pool/_delete/AutoThreadPool.h
Executable file
@@ -0,0 +1,15 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tjp/core/types/Types.h>
|
||||
|
||||
namespace tjp::core::thread_pool {
|
||||
|
||||
template<typename P>
|
||||
struct AutoThreadPool;
|
||||
|
||||
template<typename P, size_t minThreads_, size_t maxThreads_>
|
||||
struct AutoThreadPoolSized;
|
||||
|
||||
} // namespace
|
||||
50
tjp/core/thread_pool/_delete/AutoThreadPool.hpp
Executable file
50
tjp/core/thread_pool/_delete/AutoThreadPool.hpp
Executable file
@@ -0,0 +1,50 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AutoThreadPool.h"
|
||||
#include "ctpl_stl.h"
|
||||
|
||||
namespace tjp::core::thread_pool {
|
||||
|
||||
template<typename P>
|
||||
struct AutoThreadPool
|
||||
{
|
||||
using Pool = P;
|
||||
Pool pool;
|
||||
size_t minThreads, maxThreads;
|
||||
|
||||
AutoThreadPool(size_t minThreads_, size_t maxThreads_) :
|
||||
pool((int)minThreads_),
|
||||
minThreads(minThreads_),
|
||||
maxThreads(maxThreads_)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename ...T>
|
||||
auto push(T &&...t)
|
||||
{
|
||||
adjustBeforePush();
|
||||
pool.push(t...);
|
||||
}
|
||||
|
||||
void adjustBeforePush ()
|
||||
{
|
||||
auto numThreads = pool.size();
|
||||
auto numIdle = pool.n_idle();
|
||||
|
||||
if (numIdle < 1 && numThreads < maxThreads)
|
||||
pool.resize(numThreads + 1);
|
||||
|
||||
if (numIdle > 2 && numThreads > minThreads)
|
||||
pool.resize(numThreads - 1);
|
||||
}
|
||||
} ;
|
||||
|
||||
template<typename P, size_t minThreads_, size_t maxThreads_>
|
||||
struct AutoThreadPoolSized : AutoThreadPool<P>
|
||||
{
|
||||
AutoThreadPoolSized () : AutoThreadPool(minThreads_, maxThreads_) {}
|
||||
} ;
|
||||
|
||||
} // namespace
|
||||
253
tjp/core/thread_pool/_delete/ctpl_stl.h
Executable file
253
tjp/core/thread_pool/_delete/ctpl_stl.h
Executable file
@@ -0,0 +1,253 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
/*********************************************************
|
||||
*
|
||||
* Copyright (C) 2014 by Vitaliy Vitsentiy
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*********************************************************/
|
||||
|
||||
|
||||
#ifndef __ctpl_stl_thread_pool_H__
|
||||
#define __ctpl_stl_thread_pool_H__
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
|
||||
|
||||
// thread pool to run user's functors with signature
|
||||
// ret func(int id, other_params)
|
||||
// where id is the index of the thread that runs the functor
|
||||
// ret is some return type
|
||||
|
||||
|
||||
namespace ctpl {
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
class Queue {
|
||||
public:
|
||||
bool push(T const & value) {
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
this->q.push(value);
|
||||
return true;
|
||||
}
|
||||
// deletes the retrieved element, do not use for non integral types
|
||||
bool pop(T & v) {
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
if (this->q.empty())
|
||||
return false;
|
||||
v = this->q.front();
|
||||
this->q.pop();
|
||||
return true;
|
||||
}
|
||||
bool empty() {
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
return this->q.empty();
|
||||
}
|
||||
private:
|
||||
std::queue<T> q;
|
||||
std::mutex mutex;
|
||||
};
|
||||
}
|
||||
|
||||
class thread_pool {
|
||||
|
||||
public:
|
||||
|
||||
thread_pool() { this->init(); }
|
||||
thread_pool(int nThreads) { this->init(); this->resize(nThreads); }
|
||||
|
||||
// the destructor waits for all the functions in the queue to be finished
|
||||
~thread_pool() {
|
||||
this->stop(true);
|
||||
}
|
||||
|
||||
// get the number of running threads in the pool
|
||||
int size() { return static_cast<int>(this->threads.size()); }
|
||||
|
||||
// number of idle threads
|
||||
int n_idle() { return this->nWaiting; }
|
||||
std::thread & get_thread(int i) { return *this->threads[i]; }
|
||||
|
||||
// change the number of threads in the pool
|
||||
// should be called from one thread, otherwise be careful to not interleave, also with this->stop()
|
||||
// nThreads must be >= 0
|
||||
void resize(int nThreads) {
|
||||
if (!this->isStop && !this->isDone) {
|
||||
int oldNThreads = static_cast<int>(this->threads.size());
|
||||
if (oldNThreads <= nThreads) { // if the number of threads is increased
|
||||
this->threads.resize(nThreads);
|
||||
this->flags.resize(nThreads);
|
||||
|
||||
for (int i = oldNThreads; i < nThreads; ++i) {
|
||||
this->flags[i] = std::make_shared<std::atomic<bool>>(false);
|
||||
this->set_thread(i);
|
||||
}
|
||||
}
|
||||
else { // the number of threads is decreased
|
||||
for (int i = oldNThreads - 1; i >= nThreads; --i) {
|
||||
*this->flags[i] = true; // this thread will finish
|
||||
this->threads[i]->detach();
|
||||
}
|
||||
{
|
||||
// stop the detached threads that were waiting
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
this->cv.notify_all();
|
||||
}
|
||||
this->threads.resize(nThreads); // safe to delete because the threads are detached
|
||||
this->flags.resize(nThreads); // safe to delete because the threads have copies of shared_ptr of the flags, not originals
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// empty the queue
|
||||
void clear_queue() {
|
||||
std::function<void(int id)> * _f;
|
||||
while (this->q.pop(_f))
|
||||
delete _f; // empty the queue
|
||||
}
|
||||
|
||||
// pops a functional wrapper to the original function
|
||||
std::function<void(int)> pop() {
|
||||
std::function<void(int id)> * _f = nullptr;
|
||||
this->q.pop(_f);
|
||||
std::unique_ptr<std::function<void(int id)>> func(_f); // at return, delete the function even if an exception occurred
|
||||
std::function<void(int)> f;
|
||||
if (_f)
|
||||
f = *_f;
|
||||
return f;
|
||||
}
|
||||
|
||||
// wait for all computing threads to finish and stop all threads
|
||||
// may be called asynchronously to not pause the calling thread while waiting
|
||||
// if isWait == true, all the functions in the queue are run, otherwise the queue is cleared without running the functions
|
||||
void stop(bool isWait = false) {
|
||||
if (!isWait) {
|
||||
if (this->isStop)
|
||||
return;
|
||||
this->isStop = true;
|
||||
for (int i = 0, n = this->size(); i < n; ++i) {
|
||||
*this->flags[i] = true; // command the threads to stop
|
||||
}
|
||||
this->clear_queue(); // empty the queue
|
||||
}
|
||||
else {
|
||||
if (this->isDone || this->isStop)
|
||||
return;
|
||||
this->isDone = true; // give the waiting threads a command to finish
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
this->cv.notify_all(); // stop all waiting threads
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(this->threads.size()); ++i) { // wait for the computing threads to finish
|
||||
if (this->threads[i]->joinable())
|
||||
this->threads[i]->join();
|
||||
}
|
||||
// if there were no threads in the pool but some functors in the queue, the functors are not deleted by the threads
|
||||
// therefore delete them here
|
||||
this->clear_queue();
|
||||
this->threads.clear();
|
||||
this->flags.clear();
|
||||
}
|
||||
|
||||
template<typename F, typename... Rest>
|
||||
auto push(F && f, Rest&&... rest) ->std::future<decltype(f(0, rest...))> {
|
||||
auto pck = std::make_shared<std::packaged_task<decltype(f(0, rest...))(int)>>(
|
||||
std::bind(std::forward<F>(f), std::placeholders::_1, std::forward<Rest>(rest)...)
|
||||
);
|
||||
auto _f = new std::function<void(int id)>([pck](int id) {
|
||||
(*pck)(id);
|
||||
});
|
||||
this->q.push(_f);
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
this->cv.notify_one();
|
||||
return pck->get_future();
|
||||
}
|
||||
|
||||
// run the user's function that excepts argument int - id of the running thread. returned value is templatized
|
||||
// operator returns std::future, where the user can get the result and rethrow the catched exceptins
|
||||
template<typename F>
|
||||
auto push(F && f) ->std::future<decltype(f(0))> {
|
||||
auto pck = std::make_shared<std::packaged_task<decltype(f(0))(int)>>(std::forward<F>(f));
|
||||
auto _f = new std::function<void(int id)>([pck](int id) {
|
||||
(*pck)(id);
|
||||
});
|
||||
this->q.push(_f);
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
this->cv.notify_one();
|
||||
return pck->get_future();
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
// deleted
|
||||
thread_pool(const thread_pool &);// = delete;
|
||||
thread_pool(thread_pool &&);// = delete;
|
||||
thread_pool & operator=(const thread_pool &);// = delete;
|
||||
thread_pool & operator=(thread_pool &&);// = delete;
|
||||
|
||||
void set_thread(int i) {
|
||||
std::shared_ptr<std::atomic<bool>> flag(this->flags[i]); // a copy of the shared ptr to the flag
|
||||
auto f = [this, i, flag/* a copy of the shared ptr to the flag */]() {
|
||||
std::atomic<bool> & _flag = *flag;
|
||||
std::function<void(int id)> * _f;
|
||||
bool isPop = this->q.pop(_f);
|
||||
while (true) {
|
||||
while (isPop) { // if there is anything in the queue
|
||||
std::unique_ptr<std::function<void(int id)>> func(_f); // at return, delete the function even if an exception occurred
|
||||
(*_f)(i);
|
||||
if (_flag)
|
||||
return; // the thread is wanted to stop, return even if the queue is not empty yet
|
||||
else
|
||||
isPop = this->q.pop(_f);
|
||||
}
|
||||
// the queue is empty here, wait for the next command
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
++this->nWaiting;
|
||||
this->cv.wait(lock, [this, &_f, &isPop, &_flag](){ isPop = this->q.pop(_f); return isPop || this->isDone || _flag; });
|
||||
--this->nWaiting;
|
||||
if (!isPop)
|
||||
return; // if the queue is empty and this->isDone == true or *flag then return
|
||||
}
|
||||
};
|
||||
this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique()
|
||||
}
|
||||
|
||||
void init() { this->nWaiting = 0; this->isStop = false; this->isDone = false; }
|
||||
|
||||
std::vector<std::unique_ptr<std::thread>> threads;
|
||||
std::vector<std::shared_ptr<std::atomic<bool>>> flags;
|
||||
detail::Queue<std::function<void(int id)> *> q;
|
||||
std::atomic<bool> isDone;
|
||||
std::atomic<bool> isStop;
|
||||
std::atomic<int> nWaiting; // how many threads are waiting
|
||||
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __ctpl_stl_thread_pool_H__
|
||||
122
tjp/core/thread_pool/_tests/AutoThreadPool.cpp
Executable file
122
tjp/core/thread_pool/_tests/AutoThreadPool.cpp
Executable file
@@ -0,0 +1,122 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#include <tjp/core/thread_pool/AutoThreadPool.hpp>
|
||||
#include <tjp/core/thread_pool/FunctionalProcessor.hpp>
|
||||
|
||||
#include <tjp/core/iterators/range.hpp>
|
||||
#include <tjp/core/algorithm/unused.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <tjp/core/log/Log.h>
|
||||
#include <tjp/core/log/LogOf.h>
|
||||
|
||||
namespace tjp::core::thread_pool {
|
||||
namespace {
|
||||
|
||||
SCENARIO("AutoThreadPool")
|
||||
{
|
||||
xLogActivateStory("core::autothreadpool");
|
||||
|
||||
GIVEN("thread pool [0,4]")
|
||||
{
|
||||
AutoThreadPool<FunctionalProcessor> pool(0,4);
|
||||
|
||||
auto jobTime = std::chrono::seconds(1);
|
||||
WHEN("to the max jobs")
|
||||
{
|
||||
for (auto i : range(pool.maxThreads))
|
||||
{
|
||||
unused(i);
|
||||
|
||||
pool.push([=](ThreadID id) {
|
||||
std::this_thread::sleep_for(jobTime);
|
||||
});
|
||||
}
|
||||
|
||||
REQUIRE(pool.pool.size() == pool.maxThreads);
|
||||
}
|
||||
|
||||
WHEN("to the max jobs and let cull")
|
||||
{
|
||||
for (auto i : range(pool.maxThreads))
|
||||
{
|
||||
unused(i);
|
||||
|
||||
pool.push([=](auto id) {
|
||||
std::this_thread::sleep_for(jobTime);
|
||||
});
|
||||
}
|
||||
|
||||
REQUIRE(pool.pool.size() == pool.maxThreads);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
REQUIRE(pool.pool.size() == pool.maxThreads);
|
||||
|
||||
std::this_thread::sleep_for(jobTime);
|
||||
for (auto i : range(60))
|
||||
{
|
||||
unused(i);
|
||||
|
||||
sLogDebug("testing", logVar(pool.averageDesiredThreads) << logVar(pool.pool.size()) << logVar(pool.pool.num_requested()));
|
||||
|
||||
if (pool.pool.size() == pool.minThreads)
|
||||
break;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
REQUIRE(pool.pool.size() == pool.minThreads);
|
||||
}
|
||||
|
||||
WHEN("to the max jobs, cull to half and then up")
|
||||
{
|
||||
for (auto i : range(pool.maxThreads))
|
||||
{
|
||||
unused(i);
|
||||
|
||||
pool.push([=](auto id) {
|
||||
std::this_thread::sleep_for(jobTime);
|
||||
});
|
||||
}
|
||||
|
||||
REQUIRE(pool.pool.size() == pool.maxThreads);
|
||||
|
||||
std::this_thread::sleep_for(jobTime);
|
||||
|
||||
while(pool.pool.size() > pool.maxThreads/2)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
sLogDebug("testing", logVar(pool.averageDesiredThreads) << logVar(pool.pool.size()) << logVar(pool.pool.num_requested()));
|
||||
}
|
||||
|
||||
for (auto i : range(pool.maxThreads))
|
||||
{
|
||||
unused(i);
|
||||
|
||||
pool.push([=](auto id) {
|
||||
std::this_thread::sleep_for(jobTime);
|
||||
});
|
||||
}
|
||||
|
||||
REQUIRE(pool.pool.size() == pool.maxThreads);
|
||||
|
||||
for (auto i : range(60))
|
||||
{
|
||||
unused(i);
|
||||
|
||||
sLogDebug("testing", logVar(pool.averageDesiredThreads) << logVar(pool.pool.size()) << logVar(pool.pool.num_requested()));
|
||||
|
||||
if (pool.pool.size() == pool.minThreads)
|
||||
break;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
REQUIRE(pool.pool.size() == pool.minThreads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user