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

См. также

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)