CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Ruby 语言入门/课时/并发
RawBlame
课时目录12 节

Ruby 语言入门

  1. 01Ruby 环境
  2. 02数据类型
  3. 03语法基础
  4. 04控制流
  5. 05方法与 Blocks
  6. 06OOP 与 Mixin
  7. 07元编程
  8. 08IO 与 Enumerable
  9. 09并发
  10. 10Gems 与框架
  11. 11测试与质量
  12. 12综合练习

并发

Thread/Fiber/Ractor。

30 分钟

并发

这一课你将能做到

  1. 用 Thread 创建和管理线程,理解 MRI 的 GIL 对并发的真实影响
  2. 用 Fiber 实现轻量级协程,说清 Fiber 与 Thread 的本质区别
  3. 用 Ractor 实现真正的并行计算,理解其无共享内存的设计哲学

为什么要学

并发是 Ruby 面试的「照妖镜」——很多人写了多年 Rails,被问到「Ruby 能不能真正并行」就露馅。GIL(全局解释器锁)让 MRI 的线程无法并行执行 Ruby 代码,但 IO 等待时可以释放;Ractor 是 Ruby 3.0 引入的真正并行方案。不懂这些,写并发代码就是踩雷。

人话:Ruby 的并发像「一家餐厅只有一个厨师(GIL)」。Thread 是多个服务员——一个服务员等菜时,另一个可以接待客人(IO 并发);但只有一个厨师在炒菜(CPU 不并行)。Ractor 是「多家分店」——每家有自己的厨师,真正并行,但分店之间不共享食材(无共享内存)。

先建立图像

Ruby 并发三剑客
┌──────────┬─────────────┬─────────────┬────────────────┐
│ 机制      │ 并行能力      │ 内存共享      │ 适用场景        │
├──────────┼─────────────┼─────────────┼────────────────┤
│ Thread   │ IO 并发       │ 共享(危险)   │ Web 请求、IO 密集 │
│ Fiber    │ 协作式切换     │ 共享         │ 异步 IO、生成器   │
│ Ractor   │ 真正并行       │ 不共享(安全) │ CPU 密集计算     │
└──────────┴─────────────┴─────────────┴────────────────┘

GIL 工作方式:
Thread A ──[Ruby 代码]──[IO 等待,释放 GIL]──▶ Thread B 获得 GIL
              ↑ 只有这里能切换

跟着做

步骤 1:Thread——IO 并发

# 基本线程创建
threads = 3.times.map do |i|
  Thread.new do
    sleep(rand(0.1..0.3))   # 模拟 IO 等待
    puts "线程 #{i} 完成"
  end
end
threads.each(&:join)   # 等待所有线程结束
puts "全部完成"

# 线程间通信:Queue(线程安全)
queue = Queue.new

producer = Thread.new do
  5.times do |i|
    queue << "任务 #{i}"
    sleep(0.05)
  end
  queue << nil   # 结束信号
end

consumer = Thread.new do
  while (item = queue.pop)
    puts "消费: #{item}"
  end
end

producer.join
consumer.join

# 共享变量的危险:竞态条件
counter = 0
threads = 10.times.map do
  Thread.new { 1000.times { counter += 1 } }
end
threads.each(&:join)
puts "counter = #{counter}"   # 大概率不是 10000!

# 解决:Mutex(互斥锁)
counter = 0
mutex = Mutex.new
threads = 10.times.map do
  Thread.new { 1000.times { mutex.synchronize { counter += 1 } } }
end
threads.each(&:join)
puts "加锁后 counter = #{counter}"   # => 10000

步骤 2:Fiber——协作式协程

# Fiber 是轻量级执行单元,主动让出控制权
fiber = Fiber.new do |first|
  puts "Fiber 收到: #{first}"
  second = Fiber.yield("第一次让出")
  puts "Fiber 收到: #{second}"
  "Fiber 结束"
end

puts fiber.resume("开始")      # => Fiber 收到: 开始
                              # => 第一次让出
puts fiber.resume("继续")      # => Fiber 收到: 继续
                              # => Fiber 结束

# Fiber 实现生成器
def fibonacci
  Fiber.new do
    a, b = 0, 1
    loop do
      Fiber.yield(a)
      a, b = b, a + b
    end
  end
end

fib = fibonacci
10.times { puts fib.resume }
# => 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

# Fiber 与 Thread 的关键区别:
# Thread 由操作系统调度,抢占式;
# Fiber 由程序员控制,协作式(resume/yield)。

步骤 3:Ractor——真正并行

# Ractor:Ruby 3.0+ 的真正并行方案
# 每个 Ractor 有独立的内存空间,不共享对象

# 基本创建与消息传递
ractor = Ractor.new do
  received = Ractor.receive
  "处理结果: #{received.upcase}"
end

ractor.send("hello")
puts ractor.take   # => 处理结果: HELLO

# 多个 Ractor 并行计算
def heavy_compute(n)
  (1..n).sum { |i| i * i }
end

# 串行
start = Time.now
2.times { heavy_compute(10_000_000) }
puts "串行: #{Time.now - start} 秒"

# Ractor 并行
start = Time.now
ractors = 2.times.map do
  Ractor.new { heavy_compute(10_000_000) }
end
ractors.each(&:take)
puts "Ractor 并行: #{Time.now - start} 秒"

# Ractor 的限制:不能共享可变对象
# Ractor.new { shared_array << 1 }   # 报错!IsolationError

步骤 4:concurrent-ruby 实战

# gem install concurrent-ruby
require "concurrent"

# ThreadPoolExecutor:线程池
pool = Concurrent::ThreadPoolExecutor.new(
  min_threads: 2,
  max_threads: 5,
  max_queue: 10
)

futures = 10.times.map do |i|
  Concurrent::Future.execute(executor: pool) do
    sleep(0.1)
    "任务 #{i} 完成"
  end
end

futures.each { |f| puts f.value }
pool.shutdown

# Promise:链式异步
Concurrent::Promise.execute { 10 }
  .then { |x| x * 2 }
  .then { |x| puts "结果: #{x}" }
  .rescue { |e| puts "错误: #{e}" }
  .execute

讲清楚要点

  1. GIL 的真实含义:MRI 的 GIL 保证同一时刻只有一个线程执行 Ruby 字节码,但 IO 操作(网络、文件、数据库)会释放 GIL。所以 Ruby 线程对 IO 密集有效,对 CPU 密集无效。
  2. Thread 的共享内存是双刃剑:方便通信,但需要 Mutex/Queue 保护。Ruby 的 Queue 是线程安全的,优先用它做生产者-消费者模式。
  3. Fiber 是「手动调度」的线程:Fiber 不会被抢占,只有 Fiber.yield 时才切换。适合做生成器、异步 IO(如 Async gem),不适合并行计算。
  4. Ractor 是「无共享」的并行:Ractor 之间不能访问对方对象(除非是不可变对象或显式 shareable)。通信靠消息传递(send/receive/take),天然避免竞态条件。
  5. concurrent-ruby 是生产级选择:ThreadPoolExecutor、Future、Promise、Agent 等抽象比裸 Thread 更安全。Rails 的 ActiveJob、Sidekiq 底层都依赖它。

你可能会懵的地方

  • Ruby 到底能不能并行? MRI 的 Thread 不能并行执行 Ruby 代码(GIL),但 IO 可以并发;Ractor 可以真正并行,但限制多;JRuby/TruffleRuby 无 GIL,Thread 可以真并行。看实现,不看语言。

  • Fiber 和 Thread 哪个快? Fiber 创建和切换成本更低(用户态,无系统调用);但 Fiber 不能利用多核。IO 密集用 Fiber(如 Async gem),CPU 密集用 Ractor 或多进程。

  • Ractor 为什么不能共享对象? 共享可变对象是竞态条件的根源。Ractor 强制「无共享」,通过消息传递通信,从设计上消除了数据竞争。这是 Erlang/Actor 模型的思想。

  • Rails 怎么处理并发? 多进程(Puma 的 workers)+ 多线程(Puma 的 threads)+ 异步(ActionCable 用 Fiber)。数据库连接池、请求级线程隔离是 Rails 的并发保障。

  • Ruby 3.0 的 Fiber Scheduler 是什么? 允许自定义 Fiber 的调度器(如 Async gem 的 Async::Scheduler),让阻塞 IO 自动切换 Fiber。这是 Ruby 异步生态的基础设施。

错法对照

错法问题正解
多个 Thread 直接改共享变量竞态条件,结果随机用 Mutex 或 Queue 保护
以为 Thread 能加速 CPU 计算GIL 限制,反而更慢CPU 密信用 Ractor 或多进程
Fiber 里做阻塞 IO整个进程卡住用 Fiber Scheduler + 非阻塞 IO
Ractor 里共享数组IsolationError用不可变对象或消息传递
线程不 join 就退出主线程子线程被强制终止主线程最后 threads.each(&:join)

卡点急救

  1. 程序输出结果随机,时好时坏:竞态条件。检查共享变量是否加锁,或改用 Queue/Ractor。
  2. Ractor 报 IsolationError:试图共享可变对象。把对象改成 Ractor.make_shareable(obj)(冻结)或改用消息传递。
  3. Fiber 里 sleep 不切换:Fiber 是协作式,sleep 是阻塞调用,不会自动切换。用 Fiber.scheduler 或 Async gem。

面试常问

  • GIL 是什么?对 Ruby 并发有什么影响? GIL 是 MRI 的全局解释器锁,保证同一时刻只有一个线程执行 Ruby 字节码。影响:Thread 无法并行执行 Ruby 代码,但 IO 操作会释放 GIL,所以 IO 密集场景仍可用 Thread 提升吞吐量。

  • Thread、Fiber、Ractor 的区别和适用场景? Thread:OS 级,共享内存,适合 IO 并发;Fiber:用户态,协作式,适合异步 IO 和生成器;Ractor:真正并行,无共享内存,适合 CPU 密集计算。

  • Ruby 实现并发的最佳实践? IO 密集:Puma 多进程+多线程、Sidekiq 多线程;CPU 密集:Ractor 或 JRuby;异步 IO:Async gem(Fiber Scheduler)。避免裸 Thread 共享状态,优先用 concurrent-ruby 的抽象。

自测三问

  1. MRI 的 GIL 对 Thread 的 CPU 密集任务有什么影响?
  2. Fiber 的「协作式」和 Thread 的「抢占式」有什么区别?
  3. Ractor 为什么强制无共享内存?这解决了什么问题?

小结一句话

Ruby 并发的真相是:GIL 下 Thread 只能并发不能并行,Fiber 是协作式协程,Ractor 才是未来——但无共享的设计让写并行代码像写分布式系统。

延伸

  • Ruby Ractor 官方文档
  • concurrent-ruby 官方仓库
  • 下一课:Gems 与框架——Bundler、Rack、Sinatra 与 Rails 生态。
加载本节练习…
← 上一课IO 与 Enumerable下一课 →Gems 与框架