Files
Seele/src/Engine/ThreadPool.h
T

426 lines
10 KiB
C++
Raw Normal View History

2021-10-12 14:20:30 +02:00
#pragma once
#include <thread>
2021-11-18 12:48:21 +01:00
#include <coroutine>
2021-11-05 00:01:12 +01:00
#include "MinimalEngine.h"
2021-10-23 00:22:35 +02:00
#include "Containers/List.h"
2021-12-09 12:38:02 +01:00
#include "Concepts.h"
2022-02-24 22:38:26 +01:00
#include <source_location>
2021-10-12 14:20:30 +02:00
namespace Seele
{
2021-11-01 20:25:16 +01:00
extern class ThreadPool& getGlobalThreadPool();
template<bool MainJob>
2021-12-15 00:05:42 +01:00
struct JobPromiseBase;
2021-11-01 20:25:16 +01:00
struct Event
{
public:
Event();
2021-12-02 13:00:03 +01:00
Event(nullptr_t);
2021-11-01 20:25:16 +01:00
Event(const std::string& name);
2022-03-19 22:45:30 +01:00
Event(const std::source_location& location);
2022-01-12 14:40:26 +01:00
~Event() = default;
2021-11-11 20:12:50 +01:00
auto operator<=>(const Event& other) const
{
2021-11-13 19:28:18 +01:00
return flag <=> other.flag;
2021-11-11 20:12:50 +01:00
}
2021-12-15 00:05:42 +01:00
bool operator==(const Event& other) const
{
return flag == other.flag;
}
2021-11-11 20:12:50 +01:00
Event operator co_await()
{
return *this;
}
operator bool()
{
2022-02-14 16:29:26 +01:00
std::scoped_lock lock(flag->lock);
return flag->data;
}
2021-11-11 20:12:50 +01:00
2022-03-19 22:45:30 +01:00
friend std::ostream& operator<<(std::ostream& stream, const Event& event)
{
stream
<< event.flag->location.file_name()
<< "("
<< event.flag->location.line()
<< ":"
<< event.flag->location.column()
<< "): "
<< event.flag->location.function_name();
return stream;
}
2021-11-01 20:25:16 +01:00
void raise();
void reset();
bool await_ready();
template<bool MainJob>
constexpr void await_suspend(std::coroutine_handle<JobPromiseBase<MainJob>> h);
constexpr void await_resume() {}
private:
2022-02-14 16:29:26 +01:00
struct StateStore
{
std::mutex lock;
2022-03-19 22:45:30 +01:00
std::string name;
std::source_location location;
2022-02-14 16:29:26 +01:00
bool data;
};
std::shared_ptr<StateStore> flag;
2021-11-11 20:12:50 +01:00
friend class ThreadPool;
2021-10-12 14:20:30 +02:00
};
2022-02-14 16:29:26 +01:00
template<bool MainJob>
2021-12-15 00:05:42 +01:00
struct JobBase;
template<bool MainJob>
struct JobPromiseBase
2021-11-24 12:10:23 +01:00
{
2021-12-27 15:04:53 +01:00
enum class State
{
READY,
WAITING,
SCHEDULED,
EXECUTING,
DONE
};
2022-02-24 22:38:26 +01:00
JobPromiseBase(const std::source_location& location = std::source_location::current())
2021-12-09 12:38:02 +01:00
{
2021-12-15 00:05:42 +01:00
handle = std::coroutine_handle<JobPromiseBase<MainJob>>::from_promise(*this);
2022-03-19 22:45:30 +01:00
finishedEvent = Event(location);
2021-12-09 12:38:02 +01:00
}
2022-02-14 16:29:26 +01:00
~JobPromiseBase()
{}
2021-12-15 00:05:42 +01:00
JobBase<MainJob> get_return_object() noexcept;
inline auto initial_suspend() noexcept;
2022-02-14 16:29:26 +01:00
inline auto final_suspend() noexcept;
2021-12-15 00:05:42 +01:00
void return_void() noexcept {}
2022-03-19 22:45:30 +01:00
void unhandled_exception() {
std::exception_ptr exception = std::current_exception();
std::cerr << "Unhandled exception!" << std::endl;
throw exception;
2021-12-15 00:05:42 +01:00
};
2021-11-24 12:10:23 +01:00
void resume()
{
2022-02-14 16:29:26 +01:00
std::scoped_lock lock(promiseLock);
2021-12-27 15:04:53 +01:00
if(!handle || handle.done() || executing())
2021-12-15 00:05:42 +01:00
{
2021-12-27 15:04:53 +01:00
return;
2021-12-15 00:05:42 +01:00
}
2021-12-27 15:04:53 +01:00
state = State::EXECUTING;
handle.resume();
2021-11-24 12:10:23 +01:00
}
2022-02-14 16:29:26 +01:00
void markDone()
{
state = State::DONE;
finishedEvent.raise();
if(continuation)
{
2022-02-24 22:38:26 +01:00
std::scoped_lock lock(continuation->promiseLock);
2022-02-14 16:29:26 +01:00
getGlobalThreadPool().scheduleJob(continuation);
continuation->removeRef();
}
}
2021-12-15 00:05:42 +01:00
void setContinuation(JobPromiseBase* cont)
2021-12-09 12:38:02 +01:00
{
2022-01-12 14:40:26 +01:00
std::scoped_lock lock(promiseLock, cont->promiseLock);
2022-02-24 22:38:26 +01:00
assert(cont->ready());
2022-02-14 16:29:26 +01:00
continuation = cont;
2022-01-12 14:40:26 +01:00
cont->state = State::SCHEDULED;
2022-02-14 16:29:26 +01:00
cont->addRef();
2021-12-02 13:00:03 +01:00
}
bool done()
{
2022-02-14 16:29:26 +01:00
return state == State::DONE;
2021-12-02 13:00:03 +01:00
}
2021-12-27 15:04:53 +01:00
bool scheduled()
{
return state == State::SCHEDULED;
}
bool waiting()
{
return state == State::WAITING;
}
bool executing()
{
return state == State::EXECUTING;
}
bool ready()
{
return state == State::READY;
}
2021-12-02 13:00:03 +01:00
void reset()
{
2022-01-12 14:40:26 +01:00
std::scoped_lock lock(promiseLock);
2021-12-15 00:05:42 +01:00
finishedEvent.reset();
}
void enqueue(Event& event)
{
2021-12-27 15:04:53 +01:00
if(!handle || handle.done() || waiting() || scheduled())
2021-12-22 11:42:07 +01:00
{
return;
}
2021-12-27 15:04:53 +01:00
state = State::WAITING;
2021-12-15 00:05:42 +01:00
getGlobalThreadPool().enqueueWaiting(event, this);
}
void schedule()
{
2022-01-12 14:40:26 +01:00
std::scoped_lock lock(promiseLock);
2021-12-27 15:04:53 +01:00
if(!handle || done() || !ready())
2021-12-15 00:05:42 +01:00
{
return;
}
2022-02-24 22:38:26 +01:00
state = State::SCHEDULED;
2022-01-12 14:40:26 +01:00
getGlobalThreadPool().scheduleJob(this);
2021-12-15 00:05:42 +01:00
}
2022-02-14 16:29:26 +01:00
void addRef()
{
numRefs++;
}
void removeRef()
{
2022-03-19 22:45:30 +01:00
if(--numRefs < 1)
2022-02-14 16:29:26 +01:00
{
2022-03-19 22:45:30 +01:00
if(done())
{
handle.destroy();
}
else
{
schedule();
}
}
2022-02-14 16:29:26 +01:00
}
2021-12-15 00:05:42 +01:00
std::mutex promiseLock;
std::coroutine_handle<JobPromiseBase> handle;
2022-02-14 16:29:26 +01:00
JobPromiseBase* continuation = nullptr;
std::atomic_uint64_t numRefs = 0;
2021-12-15 00:05:42 +01:00
Event finishedEvent;
2021-12-27 15:04:53 +01:00
State state = State::READY;
2021-12-15 00:05:42 +01:00
};
template<bool MainJob = false>
struct JobBase
{
public:
using promise_type = JobPromiseBase<MainJob>;
explicit JobBase()
: promise(nullptr)
{
}
explicit JobBase(JobPromiseBase<MainJob>* promise)
: promise(promise)
{
}
2022-02-14 16:29:26 +01:00
JobBase(const JobBase& other)
{
std::scoped_lock lock(other.promise->promiseLock);
promise = other.promise;
promise->addRef();
}
JobBase(JobBase&& other)
{
std::scoped_lock lock(other.promise->promiseLock);
promise = other.promise;
other.promise = nullptr;
}
2021-12-15 00:05:42 +01:00
~JobBase()
{
if(promise)
{
2022-03-19 22:45:30 +01:00
//promise->schedule();
2022-02-14 16:29:26 +01:00
promise->removeRef();
2021-12-15 00:05:42 +01:00
}
}
2022-02-14 16:29:26 +01:00
JobBase& operator=(const JobBase& other)
{
if(this != &other)
{
std::scoped_lock lock(other.promise->promiseLock);
promise = other.promise;
promise->addRef();
}
return *this;
}
JobBase& operator=(JobBase&& other)
{
if(this != &other)
{
std::scoped_lock lock(other.promise->promiseLock);
promise = other.promise;
other.promise = nullptr;
}
return *this;
}
2021-12-15 00:05:42 +01:00
void resume()
{
promise->resume();
}
template<std::invocable Callable>
2021-12-22 11:42:07 +01:00
inline JobBase&& then(Callable callable)
2021-12-15 00:05:42 +01:00
{
return then(callable());
}
2021-12-22 11:42:07 +01:00
JobBase&& then(JobBase&& continuation)
2021-12-15 00:05:42 +01:00
{
promise->setContinuation(continuation.promise);
2022-02-24 22:38:26 +01:00
promise->schedule();
2021-12-22 11:42:07 +01:00
return std::move(continuation);
2021-12-15 00:05:42 +01:00
}
bool done()
{
return promise->done();
}
2022-02-24 22:38:26 +01:00
Event operator co_await() const
2021-12-02 13:00:03 +01:00
{
2022-03-19 22:45:30 +01:00
// the co_await operator keeps a reference to this, it won't
2022-02-14 16:29:26 +01:00
// be scheduled from the destructor
2021-12-27 15:04:53 +01:00
promise->schedule();
2022-02-24 22:38:26 +01:00
return promise->finishedEvent;
2021-12-02 13:00:03 +01:00
}
2021-12-09 12:38:02 +01:00
static JobBase all() = delete;
2022-03-19 22:45:30 +01:00
template<std::ranges::range Iterable>
requires std::same_as<std::ranges::range_value_t<Iterable>, JobBase>
static JobBase all(Iterable collection)
{
getGlobalThreadPool().scheduleBatch(collection);
for(auto it : collection)
{
co_await it;
}
}
template<std::ranges::range Iterable>
2022-02-14 16:29:26 +01:00
static JobBase all(Iterable collection)
2021-12-02 13:00:03 +01:00
{
2022-02-14 16:29:26 +01:00
for(auto it : collection)
2021-12-02 13:00:03 +01:00
{
co_await it;
}
}
2022-02-14 16:29:26 +01:00
template<typename... Awaitable>
static JobBase all(Awaitable&&... jobs)
{
return JobBase::all(Array{jobs...});
}
2022-03-19 22:45:30 +01:00
template<typename JobFunc, std::ranges::input_range Iterable>
requires std::invocable<JobFunc, std::ranges::range_reference_t<Iterable>>
static JobBase launchJobs(JobFunc&& func, Iterable params)
2021-12-02 13:00:03 +01:00
{
2022-03-19 22:45:30 +01:00
Array<JobBase> jobs;
2021-12-02 13:00:03 +01:00
for(auto&& param : params)
{
2022-03-19 22:45:30 +01:00
JobBase base = func(param);
jobs.add(base);
2021-12-02 13:00:03 +01:00
}
2022-03-19 22:45:30 +01:00
getGlobalThreadPool().scheduleBatch(jobs);
2021-12-02 13:00:03 +01:00
for(auto job : jobs)
{
co_await job;
}
}
2021-11-24 12:10:23 +01:00
private:
2021-12-15 00:05:42 +01:00
JobPromiseBase<MainJob>* promise;
2022-03-19 22:45:30 +01:00
friend class ThreadPool;
2021-11-24 12:10:23 +01:00
};
using MainJob = JobBase<true>;
using Job = JobBase<false>;
2021-12-15 00:05:42 +01:00
using MainPromise = JobPromiseBase<true>;
using Promise = JobPromiseBase<false>;
2021-11-24 12:10:23 +01:00
2021-10-12 14:20:30 +02:00
class ThreadPool
{
public:
2022-02-14 16:29:26 +01:00
ThreadPool(uint32 threadCount = std::thread::hardware_concurrency());
2021-10-12 14:20:30 +02:00
virtual ~ThreadPool();
2022-03-19 22:45:30 +01:00
void waitIdle();
2022-01-12 14:40:26 +01:00
// Adds a job to the waiting queue for event
2021-12-15 00:05:42 +01:00
void enqueueWaiting(Event& event, Promise* job);
2022-01-12 14:40:26 +01:00
// Adds a job to the waiting queue for event
2021-12-15 00:05:42 +01:00
void enqueueWaiting(Event& event, MainPromise* job);
2022-01-12 14:40:26 +01:00
void scheduleJob(Promise* job);
void scheduleJob(MainPromise* job);
2022-03-19 22:45:30 +01:00
template<std::ranges::range Iterable>
requires std::same_as<std::ranges::range_value_t<Iterable>, MainJob>
void scheduleBatch(Iterable jobs)
{
std::scoped_lock lock(mainJobLock);
for(auto job : jobs)
{
job.promise->addRef();
job.promise->state = JobPromiseBase<true>::State::SCHEDULED;
mainJobs.add(job.promise);
}
mainJobCV.notify_one();
}
template<std::ranges::range Iterable>
requires std::same_as<std::ranges::range_value_t<Iterable>, Job>
void scheduleBatch(Iterable jobs)
{
std::scoped_lock lock(jobQueueLock);
for(auto job : jobs)
{
job.promise->addRef();
job.promise->state = JobPromiseBase<false>::State::SCHEDULED;
jobQueue.add(job.promise);
}
jobQueueCV.notify_all();
}
2021-11-24 12:10:23 +01:00
void notify(Event& event);
2022-03-19 22:45:30 +01:00
void mainLoop();
void threadLoop();
2021-10-12 14:20:30 +02:00
private:
2021-11-01 20:25:16 +01:00
std::atomic_bool running;
2022-03-19 22:45:30 +01:00
std::mutex numIdlingLock;
std::condition_variable numIdlingIncr;
uint32 numIdling;
Array<std::thread> workers;
2021-11-01 20:25:16 +01:00
2022-03-19 22:45:30 +01:00
List<MainPromise*> mainJobs;
2021-11-01 20:25:16 +01:00
std::mutex mainJobLock;
2021-11-11 20:12:50 +01:00
std::condition_variable mainJobCV;
2021-11-01 20:25:16 +01:00
2022-03-19 22:45:30 +01:00
List<Promise*> jobQueue;
2021-11-01 20:25:16 +01:00
std::mutex jobQueueLock;
2021-11-11 20:12:50 +01:00
std::condition_variable jobQueueCV;
2021-11-01 20:25:16 +01:00
2022-03-19 22:45:30 +01:00
Map<Event, List<MainPromise*>> waitingMainJobs;
2021-11-01 20:25:16 +01:00
std::mutex waitingMainLock;
2022-03-19 22:45:30 +01:00
Map<Event, List<Promise*>> waitingJobs;
2021-12-15 00:05:42 +01:00
std::mutex waitingLock;
2022-03-19 22:45:30 +01:00
uint32 maxLocalQueueSize = 50;
2021-12-15 00:05:42 +01:00
};
2021-11-01 20:25:16 +01:00
template<bool MainJob>
2021-12-09 12:38:02 +01:00
inline JobBase<MainJob> JobPromiseBase<MainJob>::get_return_object() noexcept
{
2022-02-14 16:29:26 +01:00
numRefs++;
2021-12-15 00:05:42 +01:00
return JobBase<MainJob>(this);
2021-11-01 20:25:16 +01:00
}
template<bool MainJob>
2021-12-09 12:38:02 +01:00
inline auto JobPromiseBase<MainJob>::initial_suspend() noexcept
2021-11-01 20:25:16 +01:00
{
2021-12-15 00:05:42 +01:00
return std::suspend_always{};
2021-11-01 20:25:16 +01:00
}
2022-01-12 14:40:26 +01:00
2021-12-02 13:00:03 +01:00
template<bool MainJob>
2022-02-14 16:29:26 +01:00
inline auto JobPromiseBase<MainJob>::final_suspend() noexcept
2021-12-02 13:00:03 +01:00
{
2022-02-14 16:29:26 +01:00
markDone();
return std::suspend_always{};
2021-12-02 13:00:03 +01:00
}
2021-11-01 20:25:16 +01:00
template<bool MainJob>
inline constexpr void Event::await_suspend(std::coroutine_handle<JobPromiseBase<MainJob>> h)
{
2021-12-15 00:05:42 +01:00
h.promise().enqueue(*this);
2022-02-14 16:29:26 +01:00
flag->lock.unlock();
2021-11-01 20:25:16 +01:00
}
2021-10-12 14:20:30 +02:00
} // namespace Seele