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() , 它將等待中的執行緒完成(如果還沒有完成), 然後妥善處理它.

警告

在 Windows 上,在執行時間建立執行緒速度很慢,應該避免,以防止卡頓出現。應改用訊號量(本頁稍後將對此進行解釋)。

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)