From 7855e1726774b3c81a9945f8f0f9619bdc62b703 Mon Sep 17 00:00:00 2001 From: Ian Snyder <34639515+i-snyder@users.noreply.github.com> Date: Tue, 19 Nov 2024 03:37:59 -0600 Subject: [PATCH] Update using_multiple_threads.rst (#8752) * Update using_multiple_threads.rst Update using_multiple_threads.rst Adding C++ demos. It doesn't mirror the GDScript exactly, as I found this to be a clearer example of what the thread was doing when testing with multiple threads. All credit to "coder" from this Godot Forum thread: https://forum.godotengine.org/t/gdextension-c-async/36756/7?u=i-snyder With applied suggestions from AThousandShips code review (many thanks!!) --------- Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> --- .../performance/using_multiple_threads.rst | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) diff --git a/tutorials/performance/using_multiple_threads.rst b/tutorials/performance/using_multiple_threads.rst index 2382d0b74..dff0c8856 100644 --- a/tutorials/performance/using_multiple_threads.rst +++ b/tutorials/performance/using_multiple_threads.rst @@ -47,6 +47,93 @@ To create a thread, use the following code: func _exit_tree(): thread.wait_to_finish() + .. code-tab:: cpp C++ .H File + + #ifndef MULTITHREADING_DEMO_H + #define MULTITHREADING_DEMO_H + + #include + #include + + namespace godot { + class MultithreadingDemo : public Node { + GDCLASS(MultithreadingDemo, Node); + + private: + Ref worker; + + protected: + static void _bind_methods(); + void _notification(int p_what); + + public: + MultithreadingDemo(); + ~MultithreadingDemo(); + + void demo_threaded_function(); + }; + } // namespace godot + + #endif // MULTITHREADING_DEMO_H + + .. code-tab:: cpp C++ .CPP File + + #include "multithreading_demo.h" + + #include + #include + #include + #include + #include + + using namespace godot; + + void MultithreadingDemo::_bind_methods() { + ClassDB::bind_method(D_METHOD("threaded_function"), &MultithreadingDemo::demo_threaded_function); + } + + void MultithreadingDemo::_notification(int p_what) { + // Prevents this from running in the editor, only during game mode. In Godot 4.3+ use Runtime classes. + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + switch (p_what) { + case NOTIFICATION_READY: { + worker.instantiate(); + worker->start(callable_mp(this, &MultithreadingDemo::demo_threaded_function), Thread::PRIORITY_NORMAL); + } break; + case NOTIFICATION_EXIT_TREE: { // Thread must be disposed (or "joined"), for portability. + // Wait until it exits. + if (worker.is_valid()) { + worker->wait_to_finish(); + } + + worker.unref(); + } break; + } + } + + MultithreadingDemo::MultithreadingDemo() { + // Initialize any variables here. + } + + MultithreadingDemo::~MultithreadingDemo() { + // Add your cleanup here. + } + + void MultithreadingDemo::demo_threaded_function() { + UtilityFunctions::print("demo_threaded_function started!"); + int i = 0; + uint64_t start = Time::get_singleton()->get_ticks_msec(); + while (Time::get_singleton()->get_ticks_msec() - start < 5000) { + OS::get_singleton()->delay_msec(10); + i++; + } + + UtilityFunctions::print("demo_threaded_function counted to: ", i, "."); + } + Your function will, then, run in a separate thread until it returns. Even if the function has returned already, the thread must collect it, so call :ref:`Thread.wait_to_finish()`, which will @@ -112,6 +199,99 @@ Here is an example of using a Mutex: thread.wait_to_finish() print("Counter is: ", counter) # Should be 2. + .. code-tab:: cpp C++ .H File + + #ifndef MUTEX_DEMO_H + #define MUTEX_DEMO_H + + #include + #include + #include + + namespace godot { + class MutexDemo : public Node { + GDCLASS(MutexDemo, Node); + + private: + int counter = 0; + Ref mutex; + Ref thread; + + protected: + static void _bind_methods(); + void _notification(int p_what); + + public: + MutexDemo(); + ~MutexDemo(); + + void thread_function(); + }; + } // namespace godot + + #endif // MUTEX_DEMO_H + + .. code-tab:: cpp C++ .CPP File + + #include "mutex_demo.h" + + #include + #include + #include + #include + + using namespace godot; + + void MutexDemo::_bind_methods() { + ClassDB::bind_method(D_METHOD("thread_function"), &MutexDemo::thread_function); + } + + void MutexDemo::_notification(int p_what) { + // Prevents this from running in the editor, only during game mode. + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + switch (p_what) { + case NOTIFICATION_READY: { + UtilityFunctions::print("Mutex Demo Counter is starting at: ", counter); + mutex.instantiate(); + thread.instantiate(); + thread->start(callable_mp(this, &MutexDemo::thread_function), Thread::PRIORITY_NORMAL); + + // Increase value, protect it with Mutex. + mutex->lock(); + counter += 1; + UtilityFunctions::print("Mutex Demo Counter is ", counter, " after adding with Mutex protection."); + mutex->unlock(); + } break; + case NOTIFICATION_EXIT_TREE: { // Thread must be disposed (or "joined"), for portability. + // Wait until it exits. + if (thread.is_valid()) { + thread->wait_to_finish(); + } + thread.unref(); + + UtilityFunctions::print("Mutex Demo Counter is ", counter, " at EXIT_TREE."); // Should be 2. + } break; + } + } + + MutexDemo::MutexDemo() { + // Initialize any variables here. + } + + MutexDemo::~MutexDemo() { + // Add your cleanup here. + } + + // Increment the value from the thread, too. + void MutexDemo::thread_function() { + mutex->lock(); + counter += 1; + mutex->unlock(); + } + Semaphores ---------- @@ -188,3 +368,134 @@ ready to be processed: # Print the counter. print("Counter is: ", counter) + + .. code-tab:: cpp C++ .H File + + #ifndef SEMAPHORE_DEMO_H + #define SEMAPHORE_DEMO_H + + #include + #include + #include + #include + + namespace godot { + class SemaphoreDemo : public Node { + GDCLASS(SemaphoreDemo, Node); + + private: + int counter = 0; + Ref mutex; + Ref semaphore; + Ref thread; + bool exit_thread = false; + + protected: + static void _bind_methods(); + void _notification(int p_what); + + public: + SemaphoreDemo(); + ~SemaphoreDemo(); + + void thread_function(); + void increment_counter(); + int get_counter(); + }; + } // namespace godot + + #endif // SEMAPHORE_DEMO_H + + .. code-tab:: cpp C++ .CPP File + + #include "semaphore_demo.h" + + #include + #include + #include + #include + + using namespace godot; + + void SemaphoreDemo::_bind_methods() { + ClassDB::bind_method(D_METHOD("thread_function"), &SemaphoreDemo::thread_function); + } + + void SemaphoreDemo::_notification(int p_what) { + // Prevents this from running in the editor, only during game mode. + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + switch (p_what) { + case NOTIFICATION_READY: { + UtilityFunctions::print("Semaphore Demo Counter is starting at: ", counter); + mutex.instantiate(); + semaphore.instantiate(); + exit_thread = false; + + thread.instantiate(); + thread->start(callable_mp(this, &SemaphoreDemo::thread_function), Thread::PRIORITY_NORMAL); + + increment_counter(); // Call increment counter to test. + } break; + case NOTIFICATION_EXIT_TREE: { // Thread must be disposed (or "joined"), for portability. + // Set exit condition to true. + mutex->lock(); + exit_thread = true; // Protect with Mutex. + mutex->unlock(); + + // Unblock by posting. + semaphore->post(); + + // Wait until it exits. + if (thread.is_valid()) { + thread->wait_to_finish(); + } + thread.unref(); + + // Print the counter. + UtilityFunctions::print("Semaphore Demo Counter is ", get_counter(), " at EXIT_TREE."); + } break; + } + } + + SemaphoreDemo::SemaphoreDemo() { + // Initialize any variables here. + } + + SemaphoreDemo::~SemaphoreDemo() { + // Add your cleanup here. + } + + // Increment the value from the thread, too. + void SemaphoreDemo::thread_function() { + while (true) { + semaphore->wait(); // Wait until posted. + + mutex->lock(); + bool should_exit = exit_thread; // Protect with Mutex. + mutex->unlock(); + + if (should_exit) { + break; + } + + mutex->lock(); + counter += 1; // Increment counter, protect with Mutex. + mutex->unlock(); + } + } + + void SemaphoreDemo::increment_counter() { + semaphore->post(); // Make the thread process. + } + + int SemaphoreDemo::get_counter() { + mutex->lock(); + // Copy counter, protect with Mutex. + int counter_value = counter; + mutex->unlock(); + return counter_value; + } +