Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Usando múltiplas partes_paralelizáveis(threads)

Ver também

Para uma lista de primitivos de multithreading em C++, veja Múltiplas execuções (Multi-threading) / Concorrência.

Partes_Paralelizáveis

Partes_Paralelizáveis permitem a execução simultânea de código. Permite o trabalho de desafogar a execução na parte principal.

O Godot oferece suporte a partes_paralelizáveis e oferece muitas funções úteis para usar tais partes.

Nota

Se estiver usando outras linguagens (C#, C++), pode ser mais fácil usar as classes de thread que elas suportam.

Aviso

Antes de usar uma classe embutida em uma thread, leia APIs thread-safe (seguras para threads) primeiro para verificar se ela pode ser usada com segurança em uma thread.

Criando um thread

Para criar uma thread, use o seguinte código:

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

Sua função, então, rodará em uma thread separada até retornar. Mesmo que a função já tenha retornado, a thread precisa ser coletada, então chame Thread.wait_to_finish(), que esperará até que a thread termine (se ainda não terminou) e então a descartará adequadamente.

Aviso

Criar threads é uma operação lenta, especialmente no Windows. Para evitar sobrecarga de desempenho desnecessária, certifique-se de criar threads antes que o processamento pesado seja necessário, em vez de criá-las no momento exato do uso (just-in-time).

Por exemplo, se você precisar de múltiplas threads durante a jogabilidade, pode criar as threads enquanto o nível está carregando e só começar o processamento com elas de fato mais tarde.

Além disso, bloquear e desbloquear mutexes também pode ser uma operação cara. O bloqueio deve ser feito com cuidado; evite bloquear com muita frequência (o por muito tempo).

Mutexes

O acesso a objetos ou dados de várias partes_paralelizáveis nem sempre é suportado (se você fizer isso, causará comportamentos inesperados ou travamentos). Leia a documentação APIs thread-safe (seguras para threads) para entender quais APIs de mecanismo suportam acesso a múltiplas partes_paralelizáveis.

Ao processar seus próprios dados ou chamar suas próprias funções, como regra, tente evitar acessar os mesmos dados diretamente de partes_paralelizáveis diferentes. Você pode ter problemas de sincronização, pois os dados nem sempre são atualizados entre os núcleos da CPU quando modificados. Sempre use um Mutex ao acessar um dado de partes_paralelizáveis diferentes.

Ao chamar Mutex.lock(), uma parte_paralelizável garante que todas as outras partes_paralelizáveis serão bloqueadas (colocadas em estado suspenso) se tentarem bloquear o mesmo mutex. Quando o mutex é desbloqueado chamando Mutex.unlock(), as outras partes_paralelizáveis poderão prosseguir com o bloqueio (mas apenas uma de cada vez).

Eis um exemplo de uso de um 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

Às vezes, você quer que sua thread trabalhe "sob demanda". Em outras palavras, diga a ela quando trabalhar e deixe-a suspensa quando não estiver fazendo nada. Para isso, Semaphores (Semáforos) são usados. A função Semaphore.wait() é usada na thread para suspendê-la até que alguns dados cheguem.

A thread principal, em vez disso, usa Semaphore.post() para sinalizar que os dados estão prontos para serem processados:

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)