Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Utiliser plusieurs fils d'exécution

Tâches Parallèles

Les threads permettent l'exécution simultanée de code. Cela permet de décharger le travail du thread principal.

Godot supporte les threads et fournit de nombreuses fonctions pratiques pour les utiliser.

Note

Si vous utilisez d'autres langages (C#, C++), il peut être plus facile d'utiliser directement les classes de threads qu'ils supportent.

Avertissement

Avant d'utiliser une classe intégrée dans un thread, lisez d'abord Les API sûres pour plusieurs fils d'exécution pour vérifier si elle peut être utilisée en toute sécurité dans un thread.

Création d’un thread

To create a thread, use the following code:

var thread: Thread

# The thread will start here.
func _ready():
    thread = Thread.new()
    # You can bind multiple arguments to a function Callable.
    thread.start(_thread_function.bind("Wafflecopter"))


# Run here and exit.
# The argument is the bound data passed from start().
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()

Votre fonction s'exécutera alors dans un thread séparé jusqu'à ce qu'elle retourne. Même si la fonction est déjà retournée, le thread doit la collecter, alors appelez Thread.wait_to_finish(), qui attendra que le thread soit terminé (si ce n'est pas encore fait), puis supprimez-le correctement.

Avertissement

Creating threads at run-time is slow on Windows and should be avoided to prevent stuttering. Semaphores, explained later on this page, should be used instead.

Mutexes

Accéder à des objets ou des données depuis plusieurs threads n'est pas toujours supporté (si vous le faites, cela provoquera des comportements inattendus ou des plantages). Lisez Les API sûres pour plusieurs fils d'exécution pour comprendre quelles API du moteur supportent l'accès par plusieurs threads.

En règle générale, lorsque vous traitez vos propres données ou que vous appelez vos propres fonctions, essayez d'éviter d'accéder aux mêmes données directement depuis différents threads. Vous pouvez rencontrer des problèmes de synchronisation, car les données ne sont pas toujours mises à jour entre les cœurs du processeur lorsqu'elles sont modifiées. Utilisez toujours un Mutex lorsque vous accédez à une donnée depuis différents threads.

Lors de l'appel de Mutex.lock(), un thread assure que tous les autres threads seront bloqués (mis en état suspendu) s'ils tentent de verrouiller le même mutex. Lorsque le mutex est déverrouillé en appelant Mutex.unlock(), les autres threads seront autorisés à verrouiller le mutex (mais un seul à la fois).

Voici un exemple d'utilisation d'un Mutex :

var counter := 0
var mutex: Mutex
var thread: Thread


# The thread will start here.
func _ready():
    mutex = Mutex.new()
    thread = Thread.new()
    thread.start(_thread_function)

    # Increase value, protect it with Mutex.
    mutex.lock()
    counter += 1
    mutex.unlock()


# Increment the value from the thread, too.
func _thread_function():
    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.

Semaphores

Parfois, vous voulez que votre thread fonctionne "à la demande ". En d'autres termes, lui dire quand il doit travailler et le laissez suspendu quand il ne fait rien. Pour cela, on utilise Semaphores. La fonction Semaphore.wait() est utilisée dans le thread pour le suspendre jusqu'à ce que des données arrivent.

Le thread principal, au contraire, utilise Semaphore.post() pour signaler que les données sont prêtes à être traitées :

var counter := 0
var mutex: Mutex
var semaphore: Semaphore
var thread: 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(_thread_function)


func _thread_function():
    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)