複数のスレッドの使用

スレッド

スレッドはコードの同時実行を可能にします。これにより、メインスレッドの負荷を軽減します。

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 ドキュメントをお読みください。

独自のデータを処理するとき、または独自の関数を呼び出すときは、原則として、異なるスレッドから直接同じデータにアクセスしないようにしてください。データは変更時にCPUコア間で常に更新されないため、同期の問題が発生する可能性があります。異なるスレッドからデータにアクセスするときは、常にMutex を使用します。

Mutex.lock() を呼び出すと、スレッドは、同じミューテックスをロックしようとする、他の全てのスレッドをブロックされた(休止した)状態にします。Mutex.unlock() を呼び出してmutexのロックを解除すると、新たに他のスレッドがロックを続行できます(ただし、一度に1つのスレッドのみ)。

以下は、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)