线程
并发执行
线程 是 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 反馈 — 无需本地设置。