0Pricing
Ruby Academy · 课时

详解 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_s

C 扩展也可以释放它

编写良好的 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 反馈 — 无需本地设置。

此课程中的所有课时

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