详解 GVL
了解线程为何受到限制
详解 GVL 是 CoddyKit 上的免费 Ruby Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Ruby Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Ruby Academy 课程共包含 4 节课。
什么是 GVL
GVL(全局 VM 锁,历史上称为 GIL)是 CRuby(标准 Ruby 解释器)内部的一把锁。
- 它一次只允许一个线程执行 Ruby 代码。
- 它保护解释器的内部结构,避免并发损坏。
- 这意味着纯 Ruby 的 CPU 工作无法真正利用多个核心并行运行。
理解 GVL 有助于说明线程何时有用,以及何时没有用。
它为何存在
CRuby 的内部机制(对象分配、垃圾回收和 C 扩展)并非全部按照线程安全的方式编写。GVL 通过串行化 Ruby 的执行,使这些机制保持安全。
要干净地移除它非常困难,因此 CRuby 保留了它。JRuby 和 TruffleRuby 没有 GVL,可以在多个核心上运行线程。
CPU 受限的工作
对于CPU 受限的任务(数学计算、排序和解析),在线程下运行时,CRuby 无法加快速度。任意时刻只有一个线程运行 Ruby 代码,因此两个 CPU 线程大致相当于一个线程再加上额外开销。
def heavy
sum = 0
1_000_00.times { |i| sum += i }
sum
end
puts heavy
puts "CPU work does not parallelize under the GVL"输入输出会释放 GVL
关键要点是:阻塞式输入输出会释放 GVL。线程休眠、读取文件或等待套接字时,会释放这把锁,让另一个线程运行 Ruby 代码。
这就是即使存在 GVL,线程仍然非常适合输入输出受限工作的原因。
重叠等待
这里有三个线程,每个线程都会休眠。由于 sleep 会释放 GVL,因此等待过程会重叠。总耗时约为 0.2 秒,而不是 0.6 秒。
start = Time.now
threads = (1..3).map do
Thread.new { sleep 0.2 }
end
threads.each(&:join)
elapsed = Time.now - start
puts "Elapsed roughly: " + elapsed.round(1).to_sC 扩展也可以释放它
编写良好的 C 扩展可以在使用 rb_thread_call_without_gvl 执行长时间的原生计算时释放 GVL。这样,繁重的原生工作可以并行运行,同时 Ruby 线程继续执行。
因此,即使在 CRuby 上,一些库也能实现真正的并行。
线程仍然会切换
即使进行 CPU 工作,GVL 也不会永远冻结其他线程。CRuby 会定期抢占线程,让每个线程轮流执行。这种工作方式是并发的(交错进行),但不是并行的(同时进行)。
results = []
mutex = Mutex.new
threads = (1..3).map do |n|
Thread.new do
mutex.synchronize { results << n }
end
end
threads.each(&:join)
puts "Collected: " + results.sort.join(", ")并发与并行
有两个重要术语:
- 并发:任务通过交错执行来取得进展。GVL 支持这种方式。
- 并行:任务在不同核心上于完全相同的时刻运行。对于 Ruby 代码,GVL 会阻止这种方式。
线程提供并发;Ractor 提供并行。
绕过 GVL
要在 CRuby 中使用多个核心处理 CPU 工作,您可以:
- 运行多个进程(每个进程都有自己的 GVL)。
- 使用 Ractor 实现进程内并行。
- 将工作交给会释放 GVL 的 C 扩展。
对于输入输出工作,普通线程通常就足够了。
进程之间没有共享的 GVL
每个 OS 进程都运行自己的 Ruby VM,并拥有自己的 GVL。创建子进程可以实现真正的并行,但进程不共享内存,并且会使用更多资源。
puts "Main pid: " + Process.pid.to_s
puts "Each forked process has its own independent GVL"选择策略
选择指南:
- 需要大量等待?使用线程。
- CPU 工作繁重并且想利用多个核心?使用进程或 Ractor。
- 需要共享状态?使用带有
Mutex的线程,但要注意 GVL 的限制。
了解 GVL 可以避免一个经典错误:误以为 CPU 线程能够线性扩展。
快速检查
测试您对 GVL 的理解。
回顾:GVL
关于全局 VM 锁的关键要点:
- 在 CRuby 中,同一时间只有一个线程运行 Ruby 代码。
- 它保护解释器的内部结构。
- 阻塞式输入输出会释放它,从而支持重叠等待。
- CPU 受限的 Ruby 工作无法跨核心并行运行。
- 要实现真正的并行,请使用进程或 Ractor。
接下来我们将了解用于轻量级协作式并发的纤程。
常见问题解答
「详解 GVL」课时是免费的吗?
是的 — 「详解 GVL」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Ruby Academy 课程的其余内容,请升级到 CoddyKit PRO。 Ruby Academy 课程共包含 4 节课。
「详解 GVL」这节课中我会学到什么?
了解线程为何受到限制 你通过在浏览器中直接运行的动手代码来练习 Ruby Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Ruby Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Ruby Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「详解 GVL」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Ruby Academy 课中编写并运行代码吗?
能。每节 Ruby Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。