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.

Использование многопоточности

См. также

For a list of multithreading primitives in C++, see Multithreading / Concurrency.

Потоки

Потоки позволяют выполнять код одновременно. Это позволяет разгрузить работу от основного потока.

Godot поддерживает потоки и предоставляет множество удобных функций для их использования.

Примечание

При использовании других языков (C#, C++) может быть проще использовать поддерживаемые ими классы потоковой передачи.

Предупреждение

Прежде чем использовать встроенный класс в потоке, сначала прочитайте Потокобезопасные API, чтобы проверить, можно ли безопасно использовать его в потоке.

Создание потока

Чтобы создать поток, используйте следующий код:

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

В этом случае ваша функция будет выполняться в отдельном потоке до завершения работы. Даже если функция уже завершилась, поток должен её забрать, поэтому вызовите Thread.wait_to_finish(), который дождётся завершения работы потока (если он ещё не завершён), а затем должным образом уничтожит её.

Предупреждение

Создание потоков — медленная операция, особенно в Windows. Чтобы избежать ненужного снижения производительности, создавайте потоки до того, как потребуется интенсивная обработка, а не создавайте их непосредственно перед выполнением.

Например, если вам нужно несколько потоков во время игры, вы можете создавать потоки во время загрузки уровня и начинать обработку с ними только позже.

Кроме того, блокировка и разблокировка мьютексов может быть дорогостоящей операцией. Блокировку следует выполнять осторожно; избегайте слишком частой (или слишком длительной) блокировки.

Мьютексы

Доступ к объектам или данным из нескольких потоков поддерживается не всегда (это может привести к непредвиденному поведению или сбоям). Ознакомьтесь с документацией Потокобезопасные API, чтобы понять, какие API движка поддерживают многопоточный доступ.

При обработке собственных данных или вызове собственных функций, как правило, старайтесь избегать прямого доступа к одним и тем же данным из разных потоков. Это может привести к проблемам синхронизации, поскольку данные не всегда обновляются между ядрами процессора при изменении. Всегда используйте Mutex при доступе к данным из разных потоков.

При вызове метода Mutex.lock() поток гарантирует, что все остальные потоки будут заблокированы (переведены в состояние ожидания) при попытке заблокировать тот же мьютекс. Когда мьютекс разблокируется вызовом метода Mutex.unlock(), остальным потокам будет разрешено продолжить блокировку (но только по одному за раз).

Вот пример использования мьютекса:

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. Функция Semaphore.wait() используется в потоке для приостановки потока до получения данных.

Вместо этого основной поток использует Semaphore.post() для подачи сигнала о готовности данных к обработке:

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)