Up to date

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

Mehrere Threads verwenden

Threads

Threads ermöglichen die gleichzeitige Ausführung von Code. Es ermöglicht das Auslagern von Rechenlast aus dem Hauptprogramm.

Godot unterstützt Threads und bietet viele praktische Funktionen, um sie zu verwenden.

Bemerkung

Wenn Sie andere Sprachen verwenden (C#, C++), ist es möglicherweise einfacher, die von ihnen unterstützten Threading-Klassen zu verwenden.

Warnung

Bevor Sie eine Built-in-Klasse in einem Thread verwenden, lesen Sie zuerst Thread-sichere APIs, um zu prüfen, ob sie sicher in einem Thread verwendet werden kann.

Erstellen eines Threads

Um einen Thread zu erstellen, verwenden Sie den folgenden Code:

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()

Ihre Funktion wird dann in einem separaten Thread ausgeführt, bis sie zurückkehrt. Selbst wenn die Funktion bereits zurückgekehrt ist, muss der Thread sie einsammeln, also rufen Sie die Funktion Thread.wait_to_finish() auf, die wartet, bis der Thread fertig ist (wenn er nicht bereits fertig ist), und ihn dann ordnungsgemäß entsorgt.

Warnung

Das Erstellen von Threads zur Laufzeit ist unter Windows langsam und sollte vermieden werden, um Stuttering zu vermeiden. Stattdessen sollten Semaphoren verwendet werden, die weiter unten auf dieser Seite erläutert werden.

Mutexes

Der Zugriff auf Objekte oder Daten von mehreren Threads aus wird nicht immer unterstützt (wenn Sie es tun, führt es zu unerwartetem Verhalten oder Abstürzen). Lesen Sie die Dokumentation Thread-sichere APIs, um zu verstehen, welche Engine-APIs den Zugriff über mehrere Threads unterstützen.

Wenn Sie Ihre eigenen Daten verarbeiten oder Ihre eigenen Funktionen aufrufen, sollten Sie in der Regel vermeiden, von verschiedenen Threads aus direkt auf dieselben Daten zuzugreifen. Es kann zu Synchronisationsproblemen kommen, da die Daten bei Änderungen nicht immer zwischen den CPU-Kernen aktualisiert werden. Verwenden Sie immer einen Mutex, wenn Sie von verschiedenen Threads aus auf ein Datenelement zugreifen.

Wenn ein Thread Mutex.lock() aufruft, stellt er sicher, dass alle anderen Threads blockiert (in den Suspended-Zustand versetzt) werden, wenn sie versuchen, denselben Mutex zu sperren. Wenn der Mutex durch den Aufruf von Mutex.unlock() entsperrt wird, können die anderen Threads mit dem Lock fortfahren (aber nur einer nach dem anderen).

Hier ist ein Beispiel für die Verwendung eines 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.

Semaphoren

Manchmal möchten Sie, dass Ihr Thread "auf Anforderung" arbeitet. Mit anderen Worten, sagen Sie ihm, wann er arbeiten soll und pausieren Sie ihn, wenn er nichts zu tun hat. Dafür werden Semaphores verwendet. Die Funktion Semaphore.wait() wird im Thread benutzt, um ihn zu pausieren, bis Daten ankommen.

Der Hauptthread verwendet stattdessen Semaphore.post(), um zu signalisieren, dass die Daten zur Verarbeitung bereit sind:

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)