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 系統上。為了避免不必要的效能損耗,應該在需要大量處理之前先建立好執行緒,而不是臨時才建立。

舉例來說,如果遊戲進行時需要多個執行緒,可以在關卡載入階段先建立執行緒,之後再開始讓它們處理任務。

另外,鎖定與解鎖 Mutex 也會消耗效能。應謹慎進行鎖定,避免過於頻繁或長時間的鎖定。

Mutex(互斥鎖)

從多個執行緒存取物件或資料並不總是被支援(如果這麼做,可能會導致非預期行為或當機)。請閱讀 執行緒安全的 API 文件,瞭解哪些引擎 API 支援多執行緒存取。

當你處理自己的資料或呼叫自己的函式時,原則上應避免不同執行緒直接存取同一份資料。這樣做可能會遇到同步問題,因為資料在 CPU 核心間並不一定會即時更新。從多個執行緒存取同一資料時,請務必使用 Mutex

當呼叫 Mutex.lock() 時,該執行緒會確保其他執行緒如果試圖 鎖定 同一個 mutex,就會被阻擋(進入暫停狀態)。當使用 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.

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)