class Thread

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-local 与 Thread-local

每个 fiber 都有自己的 Thread#[] 存储桶。当你设置一个新的 fiber-local 时,它只在此 Fiber 内可访问。举例说明:

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

此示例使用 [] 进行获取,使用 []= 进行设置 fiber-locals,你还可以使用 keys 列出给定线程的 fiber-locals,使用 key? 检查 fiber-local 是否存在。

至于 thread-locals,它们在整个线程的作用域内都可访问。给定以下示例:

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

你可以看到,thread-local :foo 传递到了 fiber 中,并在线程结束时被更改为 2

此示例使用 thread_variable_set 来创建新的 thread-locals,并使用 thread_variable_get 来引用它们。

还有 thread_variables 用于列出所有 thread-locals,以及 thread_variable? 用于检查给定的 thread-local 是否存在。

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 方法也是如此,它允许你向线程调度器提示你想让哪些线程在传递执行权时获得优先权。此方法也取决于操作系统,在某些平台上可能被忽略。