Utilisation de plusieurs threads

Sujets

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.

Création d’un thread

La création d’un thread est très simple, il suffit d’utiliser le code suivant :

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()

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.

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 APIs thread-safe 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
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.

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
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)