Up to date

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

Usando múltiples hilos

Hilos

Los hilos permiten la ejecución simultánea de código, lo que permite descargar trabajo del hilo principal.

Godot admite hilos y proporciona muchas funciones útiles para utilizarlos.

Nota

Si estás utilizando otros lenguajes como C# o C++, puede ser más fácil utilizar las clases de hilos que admiten esos lenguajes.

Advertencia

Antes de utilizar una clase incorporada en un hilo, lee APIs thread safe primero para comprobar si se puede utilizar de forma segura en un hilo.

Creando un hilo

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

Tu función se ejecutará en un hilo separado hasta que regrese. Incluso si la función ya ha regresado, el hilo debe recogerla, por lo que debes llamar a Thread.wait_to_finish(), lo cual esperará hasta que el hilo haya terminado (si aún no ha terminado) y luego lo desechará correctamente.

Advertencia

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*

Acceder a objetos o datos desde múltiples hilos no siempre es compatible (si lo haces, puede provocar comportamientos inesperados o fallos). Lee la documentación de APIs thread safe para comprender qué APIs del motor admiten el acceso desde múltiples hilos de forma segura.

Cuando proceses tus propios datos o llames a tus propias funciones, como regla general, trata de evitar acceder directamente a los mismos datos desde diferentes hilos. Puedes encontrar problemas de sincronización, ya que los datos no siempre se actualizan entre los núcleos de la CPU cuando se modifican. Siempre utiliza un Mutex al acceder a un fragmento de datos desde diferentes hilos.

Al llamar a Mutex.lock(), un hilo garantiza que todos los demás hilos se bloquearán (colocados en estado suspendido) si intentan bloquear el mismo mutex. Cuando se desbloquea el mutex llamando a Mutex.unlock(), los otros hilos podrán continuar con el bloqueo (pero solo uno a la vez).

Aquí hay un ejemplo del uso de 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.

Semáforos

A veces quieres que tu hilo trabaje "bajo demanda". En otras palabras, le indicas cuándo debe trabajar y lo suspendes cuando no está haciendo nada. Para esto, se utilizan los Semáforos. La función Semaphore.wait() se utiliza en el hilo para suspenderlo hasta que llegue algún dato.

El hilo principal, en cambio, utiliza Semaphore.post() para indicar que los datos están listos para ser procesados:

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)