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.

Використання кількох потоків

Дивись також

Список примітивів багатопотокової обробки в C++ див. Багатопотоковість / Паралелізм.

Потоки

Потоки дозволяють одночасне виконання коду. Це дозволяє знімати навантаження з основного потоку.

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(), іншим потокам буде дозволено продовжити блокування (але лише по одному).

Ось приклад використання 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.

Семафори

Іноді ви хочете, щоб ваш потік працював "на вимогу". Іншими словами, скажіть йому, коли працювати, і дозвольте йому призупинити, коли він нічого не робить. Для цього використовуються 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)