2022-07-18 12:09:19 +02:00
|
|
|
/**************************************************************************/
|
|
|
|
/* worker_thread_pool.h */
|
|
|
|
/**************************************************************************/
|
|
|
|
/* This file is part of: */
|
|
|
|
/* GODOT ENGINE */
|
|
|
|
/* https://godotengine.org */
|
|
|
|
/**************************************************************************/
|
|
|
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
|
|
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
|
|
|
/* */
|
|
|
|
/* 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/or sell 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. */
|
|
|
|
/**************************************************************************/
|
|
|
|
|
|
|
|
#ifndef WORKER_THREAD_POOL_H
|
|
|
|
#define WORKER_THREAD_POOL_H
|
|
|
|
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
#include "core/os/condition_variable.h"
|
2022-07-18 12:09:19 +02:00
|
|
|
#include "core/os/memory.h"
|
|
|
|
#include "core/os/os.h"
|
|
|
|
#include "core/os/semaphore.h"
|
|
|
|
#include "core/os/thread.h"
|
|
|
|
#include "core/templates/local_vector.h"
|
|
|
|
#include "core/templates/paged_allocator.h"
|
|
|
|
#include "core/templates/rid.h"
|
|
|
|
#include "core/templates/safe_refcount.h"
|
|
|
|
|
2023-12-29 01:27:17 +01:00
|
|
|
class CommandQueueMT;
|
|
|
|
|
2022-07-18 12:09:19 +02:00
|
|
|
class WorkerThreadPool : public Object {
|
|
|
|
GDCLASS(WorkerThreadPool, Object)
|
|
|
|
public:
|
|
|
|
enum {
|
|
|
|
INVALID_TASK_ID = -1
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef int64_t TaskID;
|
|
|
|
typedef int64_t GroupID;
|
|
|
|
|
|
|
|
private:
|
|
|
|
struct Task;
|
|
|
|
|
2022-07-23 19:12:41 +02:00
|
|
|
struct BaseTemplateUserdata {
|
|
|
|
virtual void callback() {}
|
|
|
|
virtual void callback_indexed(uint32_t p_index) {}
|
|
|
|
virtual ~BaseTemplateUserdata() {}
|
|
|
|
};
|
|
|
|
|
2022-07-18 12:09:19 +02:00
|
|
|
struct Group {
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
GroupID self = -1;
|
2022-07-18 12:09:19 +02:00
|
|
|
SafeNumeric<uint32_t> index;
|
2022-07-23 19:12:41 +02:00
|
|
|
SafeNumeric<uint32_t> completed_index;
|
2022-07-18 12:09:19 +02:00
|
|
|
uint32_t max = 0;
|
|
|
|
Semaphore done_semaphore;
|
|
|
|
SafeFlag completed;
|
|
|
|
SafeNumeric<uint32_t> finished;
|
|
|
|
uint32_t tasks_used = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Task {
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
TaskID self = -1;
|
2022-07-18 12:09:19 +02:00
|
|
|
Callable callable;
|
|
|
|
void (*native_func)(void *) = nullptr;
|
|
|
|
void (*native_group_func)(void *, uint32_t) = nullptr;
|
|
|
|
void *native_func_userdata = nullptr;
|
|
|
|
String description;
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
Semaphore done_semaphore; // For user threads awaiting.
|
2022-07-18 12:09:19 +02:00
|
|
|
bool completed = false;
|
|
|
|
Group *group = nullptr;
|
|
|
|
SelfList<Task> task_elem;
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
uint32_t waiting_pool = 0;
|
|
|
|
uint32_t waiting_user = 0;
|
2022-07-18 12:09:19 +02:00
|
|
|
bool low_priority = false;
|
2022-07-23 19:12:41 +02:00
|
|
|
BaseTemplateUserdata *template_userdata = nullptr;
|
2023-05-17 00:00:45 +02:00
|
|
|
int pool_thread_index = -1;
|
2022-07-23 19:12:41 +02:00
|
|
|
|
|
|
|
void free_template_userdata();
|
2022-07-18 12:09:19 +02:00
|
|
|
Task() :
|
|
|
|
task_elem(this) {}
|
|
|
|
};
|
|
|
|
|
2023-12-28 19:31:28 +01:00
|
|
|
static const uint32_t TASKS_PAGE_SIZE = 1024;
|
|
|
|
static const uint32_t GROUPS_PAGE_SIZE = 256;
|
|
|
|
|
|
|
|
PagedAllocator<Task, false, TASKS_PAGE_SIZE> task_allocator;
|
|
|
|
PagedAllocator<Group, false, GROUPS_PAGE_SIZE> group_allocator;
|
2022-07-18 12:09:19 +02:00
|
|
|
|
|
|
|
SelfList<Task>::List low_priority_task_queue;
|
|
|
|
SelfList<Task>::List task_queue;
|
|
|
|
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
BinaryMutex task_mutex;
|
2022-07-18 12:09:19 +02:00
|
|
|
|
|
|
|
struct ThreadData {
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
uint32_t index = 0;
|
2022-07-18 12:09:19 +02:00
|
|
|
Thread thread;
|
2023-11-09 12:29:47 +01:00
|
|
|
bool ready_for_scripting = false;
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
bool signaled = false;
|
|
|
|
Task *current_task = nullptr;
|
|
|
|
Task *awaited_task = nullptr; // Null if not awaiting the condition variable. Special value for idle-waiting.
|
|
|
|
ConditionVariable cond_var;
|
2022-07-18 12:09:19 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
TightLocalVector<ThreadData> threads;
|
2023-05-11 12:24:59 +02:00
|
|
|
bool exit_threads = false;
|
2022-07-18 12:09:19 +02:00
|
|
|
|
|
|
|
HashMap<Thread::ID, int> thread_ids;
|
2023-12-28 19:31:28 +01:00
|
|
|
HashMap<
|
|
|
|
TaskID,
|
|
|
|
Task *,
|
|
|
|
HashMapHasherDefault,
|
|
|
|
HashMapComparatorDefault<TaskID>,
|
|
|
|
PagedAllocator<HashMapElement<TaskID, Task *>, false, TASKS_PAGE_SIZE>>
|
|
|
|
tasks;
|
|
|
|
HashMap<
|
|
|
|
GroupID,
|
|
|
|
Group *,
|
|
|
|
HashMapHasherDefault,
|
|
|
|
HashMapComparatorDefault<GroupID>,
|
|
|
|
PagedAllocator<HashMapElement<GroupID, Group *>, false, GROUPS_PAGE_SIZE>>
|
|
|
|
groups;
|
2022-07-18 12:09:19 +02:00
|
|
|
|
|
|
|
uint32_t max_low_priority_threads = 0;
|
2023-05-11 12:24:59 +02:00
|
|
|
uint32_t low_priority_threads_used = 0;
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
uint32_t notify_index = 0; // For rotating across threads, no help distributing load.
|
2022-07-18 12:09:19 +02:00
|
|
|
|
|
|
|
uint64_t last_task = 1;
|
|
|
|
|
|
|
|
static void _thread_function(void *p_user);
|
|
|
|
|
|
|
|
void _process_task(Task *task);
|
|
|
|
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
void _post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, bool p_high_priority);
|
|
|
|
void _notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count);
|
2022-07-18 12:09:19 +02:00
|
|
|
|
2023-05-17 00:00:45 +02:00
|
|
|
bool _try_promote_low_priority_task();
|
|
|
|
|
2022-07-18 12:09:19 +02:00
|
|
|
static WorkerThreadPool *singleton;
|
|
|
|
|
2023-12-29 01:27:17 +01:00
|
|
|
static thread_local CommandQueueMT *flushing_cmd_queue;
|
|
|
|
|
2022-07-23 19:12:41 +02:00
|
|
|
TaskID _add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description);
|
|
|
|
GroupID _add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description);
|
|
|
|
|
|
|
|
template <class C, class M, class U>
|
|
|
|
struct TaskUserData : public BaseTemplateUserdata {
|
|
|
|
C *instance;
|
|
|
|
M method;
|
|
|
|
U userdata;
|
|
|
|
virtual void callback() override {
|
|
|
|
(instance->*method)(userdata);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class C, class M, class U>
|
|
|
|
struct GroupUserData : public BaseTemplateUserdata {
|
|
|
|
C *instance;
|
|
|
|
M method;
|
|
|
|
U userdata;
|
|
|
|
virtual void callback_indexed(uint32_t p_index) override {
|
|
|
|
(instance->*method)(p_index, userdata);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-07-18 12:09:19 +02:00
|
|
|
protected:
|
|
|
|
static void _bind_methods();
|
|
|
|
|
|
|
|
public:
|
2022-07-23 19:12:41 +02:00
|
|
|
template <class C, class M, class U>
|
|
|
|
TaskID add_template_task(C *p_instance, M p_method, U p_userdata, bool p_high_priority = false, const String &p_description = String()) {
|
|
|
|
typedef TaskUserData<C, M, U> TUD;
|
|
|
|
TUD *ud = memnew(TUD);
|
|
|
|
ud->instance = p_instance;
|
|
|
|
ud->method = p_method;
|
|
|
|
ud->userdata = p_userdata;
|
|
|
|
return _add_task(Callable(), nullptr, nullptr, ud, p_high_priority, p_description);
|
|
|
|
}
|
2022-07-18 12:09:19 +02:00
|
|
|
TaskID add_native_task(void (*p_func)(void *), void *p_userdata, bool p_high_priority = false, const String &p_description = String());
|
|
|
|
TaskID add_task(const Callable &p_action, bool p_high_priority = false, const String &p_description = String());
|
|
|
|
|
|
|
|
bool is_task_completed(TaskID p_task_id) const;
|
2023-05-17 00:00:45 +02:00
|
|
|
Error wait_for_task_completion(TaskID p_task_id);
|
2022-07-18 12:09:19 +02:00
|
|
|
|
2022-07-23 19:12:41 +02:00
|
|
|
template <class C, class M, class U>
|
|
|
|
GroupID add_template_group_task(C *p_instance, M p_method, U p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String()) {
|
2023-02-01 10:49:05 +01:00
|
|
|
typedef GroupUserData<C, M, U> GroupUD;
|
|
|
|
GroupUD *ud = memnew(GroupUD);
|
2022-07-23 19:12:41 +02:00
|
|
|
ud->instance = p_instance;
|
|
|
|
ud->method = p_method;
|
|
|
|
ud->userdata = p_userdata;
|
|
|
|
return _add_group_task(Callable(), nullptr, nullptr, ud, p_elements, p_tasks, p_high_priority, p_description);
|
|
|
|
}
|
2022-07-18 12:09:19 +02:00
|
|
|
GroupID add_native_group_task(void (*p_func)(void *, uint32_t), void *p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String());
|
|
|
|
GroupID add_group_task(const Callable &p_action, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String());
|
2022-07-23 19:12:41 +02:00
|
|
|
uint32_t get_group_processed_element_count(GroupID p_group) const;
|
2022-07-18 12:09:19 +02:00
|
|
|
bool is_group_task_completed(GroupID p_group) const;
|
|
|
|
void wait_for_group_task_completion(GroupID p_group);
|
|
|
|
|
|
|
|
_FORCE_INLINE_ int get_thread_count() const { return threads.size(); }
|
|
|
|
|
|
|
|
static WorkerThreadPool *get_singleton() { return singleton; }
|
2023-05-23 11:05:32 +02:00
|
|
|
static int get_thread_index();
|
|
|
|
|
2023-12-29 01:27:17 +01:00
|
|
|
static void thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue);
|
|
|
|
static void thread_exit_command_queue_mt_flush();
|
|
|
|
|
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
|
|
|
void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3);
|
2022-07-18 12:09:19 +02:00
|
|
|
void finish();
|
|
|
|
WorkerThreadPool();
|
|
|
|
~WorkerThreadPool();
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif // WORKER_THREAD_POOL_H
|