0Pricing
Ruby Academy · 课时

线程

并发执行

线程 是 CoddyKit 上的免费 Ruby Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Ruby Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Ruby Academy 课程共包含 4 节课。

什么是线程

线程是程序内部一条独立的执行路径。Ruby 允许您运行多个线程,让不同的工作在时间上重叠进行。

  • 主程序在线程中运行。
  • 您可以使用 Thread.new 创建更多线程。
  • 线程共享同一块内存,因此可以读写相同的变量。

当程序需要花时间等待(网络、磁盘或休眠)时,线程非常适合,因为一个线程等待时,另一个线程可以继续工作。

创建线程

向 Thread.new 传入一个代码块。该代码块会在线程中运行。调用 join,可在程序退出前等待它运行完毕。

t = Thread.new do
  puts "Hello from the new thread"
end

t.join
puts "Main thread done"

为什么 join 很重要

主线程结束时,Ruby 会退出,并且可能不会等待其他线程。join 会阻塞调用者,直到目标线程完成。

如果没有 join,工作线程可能还没来得及打印内容,程序就已经退出了。

t = Thread.new do
  sleep 0.1
  puts "Worker finished"
end

puts "Waiting..."
t.join
puts "All done"

同时运行多个线程

您可以启动一组线程,将它们收集到数组中,然后逐个对其调用 join。这是处理类似并行的输入输出工作时常见的模式。

threads = (1..3).map do |n|
  Thread.new do
    puts "Thread number " + n.to_s
  end
end

threads.each(&:join)
puts "Finished all threads"

共享变量

线程共享内存。在创建线程之前定义的变量在线程内部可见。如果两个线程写入同一个值,这种能力虽然强大,却也很危险。

counter = 0

t = Thread.new do
  counter += 1
end

t.join
puts "Counter is " + counter.to_s

竞态条件

当线程交错更新共享数据,并且结果取决于时序时,就会发生竞态条件。

  • counter += 1 实际上包含读取、加法和写入三个步骤。
  • 两个线程可能读取同一个旧值,然后都写入同一个新值,从而丢失一次递增。

解决方法是使用锁协调访问。

互斥锁

Mutex(互斥锁)确保同一时间只有一个线程运行受保护的代码。请使用 mutex.synchronize 包裹临界区。

mutex = Mutex.new
counter = 0

threads = (1..5).map do
  Thread.new do
    mutex.synchronize do
      counter += 1
    end
  end
end

threads.each(&:join)
puts "Counter is " + counter.to_s

返回值

Thread#value 会等待线程完成,并返回其代码块的值,相当于自动执行 join 并返回结果。

t = Thread.new do
  2 + 3
end

puts "Result is " + t.value.to_s

线程局部变量

每个线程都可以使用 Thread.current[:key],以符号为键存储自己的值。这些值仅属于该线程,绝不会被共享。

t = Thread.new do
  Thread.current[:name] = "worker"
  puts "Inside: " + Thread.current[:name]
end

t.join

处理异常

如果线程引发错误,该错误会一直保持静默,直到您调用 join 或 value,届时错误会被再次引发。将 Thread.abort_on_exception = true 设为真值后,错误会立即导致整个程序崩溃,这有助于调试。

t = Thread.new do
  raise "boom"
end

begin
  t.join
rescue => e
  puts "Caught: " + e.message
end

线程适用的场景

对于输入输出受限的工作,线程非常有效,因为这类工作大部分时间都在等待。对于CPU 受限的工作,线程的帮助较小,这是因为存在 GVL(下一节会介绍)。

  • 适合:下载许多网址的内容、读取许多文件。
  • 受限:在多个核心上进行繁重的数值计算。

请始终使用 Mutex 保护共享的可变状态。

快速检查

测试您对线程的理解。

回顾:线程

您已经学会了 Ruby 线程的基础知识:

  • Thread.new 启动并发执行。
  • join 会等待;value 会等待并返回结果。
  • 线程共享内存,因此会产生竞态条件。
  • 使用带有 synchronize 的 Mutex 可以保护共享状态。
  • 线程特别适合输入输出受限的工作。

接下来我们将探索线程为什么无法充分使用多个 CPU 核心:GVL。

常见问题解答

「线程」课时是免费的吗?

是的 — 「线程」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Ruby Academy 课程的其余内容,请升级到 CoddyKit PRO。 Ruby Academy 课程共包含 4 节课。

「线程」这节课中我会学到什么?

并发执行 你通过在浏览器中直接运行的动手代码来练习 Ruby Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Ruby Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Ruby Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「线程」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Ruby Academy 课中编写并运行代码吗?

能。每节 Ruby Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 线程
  2. 详解 GVL
  3. 纤程
  4. Ractor
← 返回 Ruby Academy