2023-01-12 20:49:14 +01:00
|
|
|
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Using multiple threads
|
|
|
|
======================
|
|
|
|
|
|
|
|
Threads
|
|
|
|
-------
|
|
|
|
|
|
|
|
Threads allow simultaneous execution of code. It allows off-loading work
|
|
|
|
from the main thread.
|
|
|
|
|
|
|
|
Godot supports threads and provides many handy functions to use them.
|
|
|
|
|
2023-01-12 20:55:57 +01:00
|
|
|
Note:
|
|
|
|
If using other languages (C#, C++), it may be easier to use the
|
2022-03-18 17:46:08 +01:00
|
|
|
threading classes they support.
|
|
|
|
|
2023-01-12 20:55:57 +01:00
|
|
|
Warning:
|
|
|
|
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 19:29:11 +01:00
|
|
|
Before using a built-in class in a thread, read `doc_thread_safe_apis`
|
2022-03-18 17:46:08 +01:00
|
|
|
first to check whether it can be safely used in a thread.
|
|
|
|
|
|
|
|
Creating a Thread
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
Creating a thread is very simple, just use the following code:
|
|
|
|
|
2023-01-12 18:31:02 +01:00
|
|
|
gdscript GDScript
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 18:31:02 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var thread
|
|
|
|
|
|
|
|
# The thread will start here.
|
|
|
|
func _ready():
|
|
|
|
thread = Thread.new()
|
|
|
|
# Third argument is optional userdata, it can be any variable.
|
|
|
|
thread.start(self, "_thread_function", "Wafflecopter")
|
|
|
|
|
|
|
|
|
|
|
|
# Run here and exit.
|
|
|
|
# The argument is the userdata passed from start().
|
|
|
|
# If no argument was passed, this one still needs to
|
|
|
|
# be here and it will be null.
|
|
|
|
func _thread_function(userdata):
|
|
|
|
# Print the userdata ("Wafflecopter")
|
|
|
|
print("I'm a thread! Userdata is: ", userdata)
|
|
|
|
|
|
|
|
# Thread must be disposed (or "joined"), for portability.
|
|
|
|
func _exit_tree():
|
|
|
|
thread.wait_to_finish()
|
2023-01-12 18:31:02 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
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
|
2023-01-12 20:47:54 +01:00
|
|
|
`Thread.wait_to_finish()( Thread_method_wait_to_finish )`, which will
|
2022-03-18 17:46:08 +01:00
|
|
|
wait until the thread is done (if not done yet), then properly dispose of it.
|
|
|
|
|
|
|
|
Mutexes
|
|
|
|
-------
|
|
|
|
|
|
|
|
Accessing objects or data from multiple threads is not always supported (if you
|
|
|
|
do it, it will cause unexpected behaviors or crashes). Read the
|
2023-01-12 19:29:11 +01:00
|
|
|
`doc_thread_safe_apis` documentation to understand which engine APIs
|
2022-03-18 17:46:08 +01:00
|
|
|
support multiple thread access.
|
|
|
|
|
|
|
|
When processing your own data or calling your own functions, as a rule, try to
|
|
|
|
avoid accessing the same data directly from different threads. You may run into
|
|
|
|
synchronization problems, as the data is not always updated between CPU cores
|
2023-01-12 20:47:54 +01:00
|
|
|
when modified. Always use a `Mutex( Mutex )` when accessing
|
2022-03-18 17:46:08 +01:00
|
|
|
a piece of data from different threads.
|
|
|
|
|
2023-01-12 20:47:54 +01:00
|
|
|
When calling `Mutex.lock()( Mutex_method_lock )`, a thread ensures that
|
2022-03-18 17:46:08 +01:00
|
|
|
all other threads will be blocked (put on suspended state) if they try to *lock*
|
|
|
|
the same mutex. When the mutex is unlocked by calling
|
2023-01-12 20:47:54 +01:00
|
|
|
`Mutex.unlock()( Mutex_method_unlock )`, the other threads will be
|
2022-03-18 17:46:08 +01:00
|
|
|
allowed to proceed with the lock (but only one at a time).
|
|
|
|
|
|
|
|
Here is an example of using a Mutex:
|
|
|
|
|
2023-01-12 18:31:02 +01:00
|
|
|
gdscript GDScript
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 18:31:02 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var counter = 0
|
|
|
|
var mutex
|
|
|
|
var thread
|
|
|
|
|
|
|
|
|
|
|
|
# The thread will start here.
|
|
|
|
func _ready():
|
|
|
|
mutex = Mutex.new()
|
|
|
|
thread = Thread.new()
|
|
|
|
thread.start(self, "_thread_function")
|
|
|
|
|
|
|
|
# Increase value, protect it with Mutex.
|
|
|
|
mutex.lock()
|
|
|
|
counter += 1
|
|
|
|
mutex.unlock()
|
|
|
|
|
|
|
|
|
|
|
|
# Increment the value from the thread, too.
|
|
|
|
func _thread_function(userdata):
|
|
|
|
mutex.lock()
|
|
|
|
counter += 1
|
|
|
|
mutex.unlock()
|
|
|
|
|
|
|
|
|
|
|
|
# Thread must be disposed (or "joined"), for portability.
|
|
|
|
func _exit_tree():
|
|
|
|
thread.wait_to_finish()
|
|
|
|
print("Counter is: ", counter) # Should be 2.
|
2023-01-12 18:31:02 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Semaphores
|
|
|
|
----------
|
|
|
|
|
|
|
|
Sometimes you want your thread to work *"on demand"*. In other words, tell it
|
|
|
|
when to work and let it suspend when it isn't doing anything.
|
2023-01-12 20:47:54 +01:00
|
|
|
For this, `Semaphores( Semaphore )` are used. The function
|
|
|
|
`Semaphore.wait()( Semaphore_method_wait )` is used in the thread to
|
2022-03-18 17:46:08 +01:00
|
|
|
suspend it until some data arrives.
|
|
|
|
|
|
|
|
The main thread, instead, uses
|
2023-01-12 20:47:54 +01:00
|
|
|
`Semaphore.post()( Semaphore_method_post )` to signal that data is
|
2022-03-18 17:46:08 +01:00
|
|
|
ready to be processed:
|
|
|
|
|
2023-01-12 18:31:02 +01:00
|
|
|
gdscript GDScript
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 18:31:02 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var counter = 0
|
|
|
|
var mutex
|
|
|
|
var semaphore
|
|
|
|
var thread
|
|
|
|
var exit_thread = false
|
|
|
|
|
|
|
|
|
|
|
|
# The thread will start here.
|
|
|
|
func _ready():
|
|
|
|
mutex = Mutex.new()
|
|
|
|
semaphore = Semaphore.new()
|
|
|
|
exit_thread = false
|
|
|
|
|
|
|
|
thread = Thread.new()
|
|
|
|
thread.start(self, "_thread_function")
|
|
|
|
|
|
|
|
|
|
|
|
func _thread_function(userdata):
|
|
|
|
while true:
|
|
|
|
semaphore.wait() # Wait until posted.
|
|
|
|
|
|
|
|
mutex.lock()
|
|
|
|
var should_exit = exit_thread # Protect with Mutex.
|
|
|
|
mutex.unlock()
|
|
|
|
|
|
|
|
if should_exit:
|
|
|
|
break
|
|
|
|
|
|
|
|
mutex.lock()
|
|
|
|
counter += 1 # Increment counter, protect with Mutex.
|
|
|
|
mutex.unlock()
|
|
|
|
|
|
|
|
|
|
|
|
func increment_counter():
|
|
|
|
semaphore.post() # Make the thread process.
|
|
|
|
|
|
|
|
|
|
|
|
func get_counter():
|
|
|
|
mutex.lock()
|
|
|
|
# Copy counter, protect with Mutex.
|
|
|
|
var counter_value = counter
|
|
|
|
mutex.unlock()
|
|
|
|
return counter_value
|
|
|
|
|
|
|
|
|
|
|
|
# Thread must be disposed (or "joined"), for portability.
|
|
|
|
func _exit_tree():
|
|
|
|
# Set exit condition to true.
|
|
|
|
mutex.lock()
|
|
|
|
exit_thread = true # Protect with Mutex.
|
|
|
|
mutex.unlock()
|
|
|
|
|
|
|
|
# Unblock by posting.
|
|
|
|
semaphore.post()
|
|
|
|
|
|
|
|
# Wait until it exits.
|
|
|
|
thread.wait_to_finish()
|
|
|
|
|
|
|
|
# Print the counter.
|
|
|
|
print("Counter is: ", counter)
|
2023-01-12 18:31:02 +01:00
|
|
|
```
|