使用多线程

线程

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

Godot 支持线程,并提供了许多方便使用的功能。

备注

如果使用其他语言(C#、C++),它们支持的线程类可能会更容易使用。

警告

在线程中使用内置类之前,请先阅读 线程安全的 API,检查在线程中使用是否安全。

创建线程

创建一个线程非常简单, 只需使用以下代码:

GDScript

  1. var thread
  2. # The thread will start here.
  3. func _ready():
  4. thread = Thread.new()
  5. # Third argument is optional userdata, it can be any variable.
  6. thread.start(self, "_thread_function", "Wafflecopter")
  7. # Run here and exit.
  8. # The argument is the userdata passed from start().
  9. # If no argument was passed, this one still needs to
  10. # be here and it will be null.
  11. func _thread_function(userdata):
  12. # Print the userdata ("Wafflecopter")
  13. print("I'm a thread! Userdata is: ", userdata)
  14. # Thread must be disposed (or "joined"), for portability.
  15. func _exit_tree():
  16. thread.wait_to_finish()

然后, 你的函数将在一个单独的线程中运行, 直到它返回. 即使函数已经返回, 线程也必须收集它, 所以调用 Thread.wait_to_finish() , 它将等待线程完成(如果还没有完成), 然后妥善处理它.

Mutex

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

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

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

下面是一个使用 Mutex 的例子:

GDScript

  1. var counter = 0
  2. var mutex
  3. var thread
  4. # The thread will start here.
  5. func _ready():
  6. mutex = Mutex.new()
  7. thread = Thread.new()
  8. thread.start(self, "_thread_function")
  9. # Increase value, protect it with Mutex.
  10. mutex.lock()
  11. counter += 1
  12. mutex.unlock()
  13. # Increment the value from the thread, too.
  14. func _thread_function(userdata):
  15. mutex.lock()
  16. counter += 1
  17. mutex.unlock()
  18. # Thread must be disposed (or "joined"), for portability.
  19. func _exit_tree():
  20. thread.wait_to_finish()
  21. print("Counter is: ", counter) # Should be 2.

Semaphore

有时你希望你的线程能“按需”工作。换句话说,告诉它什么时候工作,让它在不工作的时候暂停。为此,可以使用信号量 Semaphore。线程中使用函数 Semaphore.wait() 来暂停它的工作,直到有数据到达。

而主线程则使用 Semaphore.post() 来表示数据已经准备好被处理:

GDScript

  1. var counter = 0
  2. var mutex
  3. var semaphore
  4. var thread
  5. var exit_thread = false
  6. # The thread will start here.
  7. func _ready():
  8. mutex = Mutex.new()
  9. semaphore = Semaphore.new()
  10. exit_thread = false
  11. thread = Thread.new()
  12. thread.start(self, "_thread_function")
  13. func _thread_function(userdata):
  14. while true:
  15. semaphore.wait() # Wait until posted.
  16. mutex.lock()
  17. var should_exit = exit_thread # Protect with Mutex.
  18. mutex.unlock()
  19. if should_exit:
  20. break
  21. mutex.lock()
  22. counter += 1 # Increment counter, protect with Mutex.
  23. mutex.unlock()
  24. func increment_counter():
  25. semaphore.post() # Make the thread process.
  26. func get_counter():
  27. mutex.lock()
  28. # Copy counter, protect with Mutex.
  29. var counter_value = counter
  30. mutex.unlock()
  31. return counter_value
  32. # Thread must be disposed (or "joined"), for portability.
  33. func _exit_tree():
  34. # Set exit condition to true.
  35. mutex.lock()
  36. exit_thread = true # Protect with Mutex.
  37. mutex.unlock()
  38. # Unblock by posting.
  39. semaphore.post()
  40. # Wait until it exits.
  41. thread.wait_to_finish()
  42. # Print the counter.
  43. print("Counter is: ", counter)