类 Thread

线程是 Ruby 并发编程模型的实现。

需要多线程执行的程序是 Ruby 的 Thread 类的完美候选。

例如,我们可以使用 ::new 创建一个与主线程执行分离的新线程。

thr = Thread.new { puts "What's the big deal" }

然后,我们可以暂停主线程的执行,并允许我们的新线程完成,使用 join

thr.join #=> "What's the big deal"

如果我们不在主线程终止之前调用 thr.join,则包括 thr 在内的所有其他线程都将被终止。

或者,您可以使用数组一次处理多个线程,如下例所示

threads = []
threads << Thread.new { puts "What's the big deal" }
threads << Thread.new { 3.times { puts "Threads are fun!" } }

创建几个线程后,我们等待它们全部依次完成。

threads.each { |thr| thr.join }

要检索线程的最后一个值,请使用 value

thr = Thread.new { sleep 1; "Useful value" }
thr.value #=> "Useful value"

Thread 初始化

为了创建新线程,Ruby 提供了 ::new::start::fork。必须为这些方法中的每一个提供一个代码块,否则将引发 ThreadError

当子类化 Thread 类时,子类的 initialize 方法将被 ::start::fork 忽略。否则,请确保在 initialize 方法中调用 super。

Thread 终止

对于终止线程,Ruby 提供了多种方法。

类方法 ::kill 旨在退出给定的线程

thr = Thread.new { sleep }
Thread.kill(thr) # sends exit() to thr

或者,您可以使用实例方法 exit 或其任何别名 killterminate

thr.exit

Thread 状态

Ruby 提供了一些实例方法来查询给定线程的状态。要获取包含当前线程状态的字符串,请使用 status

thr = Thread.new { sleep }
thr.status # => "sleep"
thr.exit
thr.status # => false

您还可以使用 alive? 来判断线程是正在运行还是休眠,以及使用 stop? 来判断线程是已死还是休眠。

Thread 变量和作用域

由于线程是使用代码块创建的,因此相同的规则适用于其他 Ruby 代码块的变量作用域。在此代码块中创建的任何局部变量只能由此线程访问。

Fiber-本地 vs. 线程-本地

每个纤程都有自己的 Thread#[] 存储桶。当您设置一个新的纤程本地变量时,它只能在此 Fiber 中访问。为了说明

Thread.new {
  Thread.current[:foo] = "bar"
  Fiber.new {
    p Thread.current[:foo] # => nil
  }.resume
}.join

此示例使用 [] 获取和使用 []= 设置纤程本地变量,您还可以使用 keys 列出给定线程的纤程本地变量,并使用 key? 检查纤程本地变量是否存在。

对于线程本地变量,它们在线程的整个作用域内都是可访问的。给定以下示例

Thread.new{
  Thread.current.thread_variable_set(:foo, 1)
  p Thread.current.thread_variable_get(:foo) # => 1
  Fiber.new{
    Thread.current.thread_variable_set(:foo, 2)
    p Thread.current.thread_variable_get(:foo) # => 2
  }.resume
  p Thread.current.thread_variable_get(:foo)   # => 2
}.join

您可以看到线程本地变量 :foo 被传递到纤程中,并在线程结束时被更改为 2

此示例使用 thread_variable_set 创建新的线程本地变量,并使用 thread_variable_get 引用它们。

还有 thread_variables 列出所有线程本地变量,以及 thread_variable? 检查给定的线程本地变量是否存在。

Exception 处理

当在线程内部引发未处理的异常时,它将终止。默认情况下,此异常不会传播到其他线程。该异常会被存储,当另一个线程调用 valuejoin 时,该异常将在该线程中被重新引发。

t = Thread.new{ raise 'something went wrong' }
t.value #=> RuntimeError: something went wrong

可以使用 Thread#raise 实例方法从线程外部引发异常,该方法采用与 Kernel#raise 相同的参数。

设置 Thread.abort_on_exception = true、Thread#abort_on_exception = true 或 $DEBUG = true 将导致线程中引发的后续未处理异常自动在主线程中重新引发。

通过添加类方法 ::handle_interrupt,您现在可以使用线程异步处理异常。

调度

Ruby 提供了一些方法来支持在您的程序中调度线程。

第一种方法是使用类方法 ::stop,使当前正在运行的线程进入休眠状态并调度另一个线程的执行。

一旦线程处于休眠状态,您可以使用实例方法 wakeup 将您的线程标记为符合调度条件。

您还可以尝试 ::pass,它尝试将执行传递给另一个线程,但这取决于操作系统是否会切换正在运行的线程。对于 priority 也是如此,它允许您向线程调度程序提示您希望在传递执行时优先考虑哪些线程。此方法也依赖于操作系统,并且在某些平台上可能会被忽略。