pandemonium_engine_docs/usage/performance/threads/using_multiple_threads.md

191 lines
5.0 KiB
Markdown
Raw Normal View History

2023-01-12 20:49:14 +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
threading classes they support.
2023-01-12 20:55:57 +01:00
Warning:
2023-01-12 19:29:11 +01:00
Before using a built-in class in a thread, read `doc_thread_safe_apis`
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
2023-01-12 18:31:02 +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
```
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
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
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
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
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
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
2023-01-12 18:31:02 +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
```
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
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
ready to be processed:
2023-01-12 18:31:02 +01:00
gdscript GDScript
2023-01-12 18:31:02 +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
```