Up to date

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

使用多线程

线程

线程允许同时执行代码。它允许从主线程卸载工作。

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() , 它将等待线程完成(如果还没有完成), 然后妥善处理它.

警告

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.

Mutex

并不总是支持从多个线程访问对象或数据(如果你这样做, 会导致意外行为或崩溃). 请阅读 线程安全的 API 文档, 了解哪些引擎API支持多线程访问.

在处理自己的数据或调用自己的函数时, 通常情况下, 尽量避免从不同的线程直接访问相同的数据. 你可能会遇到同步问题, 因为数据被修改后,CPU核之间并不总是更新. 当从不同线程访问一个数据时, 一定要使用 Mutex .

当调用 Mutex.lock() 时, 一个线程确保所有其他线程如果试图 同一个mutex, 就会被阻塞(进入暂停状态). 当通过调用 Mutex.unlock() 来解锁该mutex时, 其他线程将被允许继续锁定(但每次只能锁定一个).

下面是一个使用 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.

Semaphore

有时你希望你的线程能“按需”工作。换句话说,告诉它什么时候工作,让它在不工作的时候暂停。为此,可以使用信号量 Semaphore。线程中使用函数 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)