Ruby 多线程
在 Ruby 中,多线程(Multithreading) 是一种并发编程技术,允许多个线程在同一进程中同时执行,从而提高程序的响应性和效率。Ruby 提供了内置的 Thread
类来实现多线程,适合 I/O 密集型任务(如网络请求、文件读写)。以下是对 Ruby 多线程的中文讲解,涵盖核心概念、创建与管理线程、同步机制、示例代码及注意事项,力求简洁清晰。
1. 多线程简介
线程是进程中的执行单元,共享同一内存空间。Ruby 的线程是绿色线程(Green Threads),由 Ruby 解释器(MRI)管理,而非操作系统原生线程。这意味着 Ruby 的多线程受 GIL(Global Interpreter Lock,全局解释器锁) 限制,影响 CPU 密集型任务的并行性,但在 I/O 密集型任务中表现良好。
适用场景:
- I/O 操作(如 HTTP 请求、数据库查询)。
- 并行处理多个任务(如爬虫、服务器客户端处理)。
- 不适合 CPU 密集型任务(如复杂计算,推荐使用多进程或 JRuby)。
2. 创建线程
使用 Thread.new
创建线程,传递一个代码块作为线程执行的内容。
基本示例
require 'thread'
# 创建线程
thread1 = Thread.new do
5.times { |i| puts "线程 1: #{i}"; sleep 1 }
end
thread2 = Thread.new do
5.times { |i| puts "线程 2: #{i}"; sleep 0.5 }
end
# 主线程继续执行
puts "主线程继续..."
# 等待线程完成
thread1.join
thread2.join
输出(示例,顺序可能因调度而异):
主线程继续...
线程 1: 0
线程 2: 0
线程 2: 1
线程 1: 1
线程 2: 2
线程 2: 3
线程 1: 2
...
说明:
Thread.new { ... }
:创建并启动线程。sleep
:模拟 I/O 等待,允许线程切换。join
:等待线程执行完成,主线程继续。
3. 线程管理
Ruby 提供了多种方法管理线程状态和行为。
常用方法
Thread.new
:创建新线程。
t = Thread.new { puts "新线程" }
Thread.current
:获取当前线程。
puts Thread.current # 输出:#<Thread:0x... run>
Thread.main
:获取主线程。Thread.list
:列出所有活跃线程。
puts Thread.list # 输出:[#<Thread:0x...>, #<Thread:0x...>]
join
:等待线程完成。kill
/terminate
:终止线程。
t = Thread.new { loop { sleep 1 } }
t.kill
status
:检查线程状态(run
,sleep
,false
(已终止)等)。
puts t.status # 输出:run 或 false
线程优先级
通过 priority
设置线程调度优先级(默认 0,越大优先级越高)。
t1 = Thread.new { 5.times { puts "低优先级"; sleep 1 } }
t2 = Thread.new { 5.times { puts "高优先级"; sleep 1 } }
t2.priority = 1
t1.join
t2.join
4. 线程同步
多线程共享内存,可能导致数据竞争或不一致问题。Ruby 提供了同步机制。
Mutex(互斥锁)
Mutex
确保同一时间只有一个线程访问共享资源。
require 'thread'
mutex = Mutex.new
counter = 0
threads = 5.times.map do
Thread.new do
mutex.synchronize do
100.times { counter += 1 }
end
end
end
threads.each(&:join)
puts "计数器: #{counter}" # 输出:500
说明:
Mutex#synchronize
:锁定代码块,防止其他线程同时执行。- 避免死锁:确保锁的顺序一致。
ConditionVariable
用于线程间的协调等待。
require 'thread'
mutex = Mutex.new
condition = ConditionVariable.new
resource = nil
producer = Thread.new do
mutex.synchronize do
resource = "数据已准备"
condition.signal # 通知消费者
end
end
consumer = Thread.new do
mutex.synchronize do
condition.wait(mutex) # 等待生产者
puts resource # 输出:数据已准备
end
end
producer.join
consumer.join
Queue
Queue
是线程安全的队列,适合生产者-消费者模型。
require 'thread'
queue = Queue.new
producer = Thread.new do
5.times { |i| queue << "任务 #{i}"; sleep 1 }
end
consumer = Thread.new do
5.times { puts "处理: #{queue.pop}" }
end
producer.join
consumer.join
输出:
处理: 任务 0
处理: 任务 1
...
5. 异常处理
线程中的异常不会影响主线程,但需显式处理。
require 'thread'
t = Thread.new do
raise "线程错误"
end
begin
t.join
rescue => e
puts "捕获线程异常: #{e.message}" # 输出:捕获线程异常: 线程错误
end
说明:
- 未处理的线程异常会导致线程终止。
- 使用
Thread#join
或Thread#value
捕获异常。
6. GIL 的影响
Ruby MRI(Matz’s Ruby Interpreter)中的 GIL 限制了多线程的 CPU 并行性:
- I/O 密集型任务:如网络请求、文件读写,线程切换有效,性能良好。
- CPU 密集型任务:如复杂计算,受 GIL 限制,无法利用多核。推荐:
- 使用 JRuby 或 Rubinius(无 GIL)。
- 使用多进程(
Process.fork
或Parallel
gem)。
7. 注意事项
- 线程安全:
- 使用
Mutex
或Queue
避免数据竞争。 - 避免修改共享对象(如数组、哈希)而不加锁。
- 性能:
- 线程切换有开销,过多线程可能降低效率。
- 使用线程池(如
Thread.pool
gem)限制线程数。 - 调试:
- 使用
Thread.list
检查活跃线程。 - 记录线程 ID(
Thread.current.object_id
)跟踪执行。 - 异常:确保每个线程的异常被捕获,否则可能导致未预期的终止。
- 替代方案:
- 异步库:如
EventMachine
或Async
gem。 - 多进程:使用
Parallel
或Fork
处理 CPU 密集型任务。 - 并发框架:如 Rails 的
ActiveJob
或 Sidekiq。
8. 综合示例:并行下载
以下示例使用多线程并行下载网页内容。
require 'thread'
require 'net/http'
urls = [
'http://example.com',
'http://example.org',
'http://example.net'
]
queue = Queue.new
urls.each { |url| queue << url }
threads = []
mutex = Mutex.new
results = []
# 创建 3 个线程处理下载
3.times do
threads << Thread.new do
while url = queue.pop(true) rescue nil
response = Net::HTTP.get_response(URI(url))
mutex.synchronize do
results << "下载 #{url}: #{response.code}"
end
end
end
end
threads.each(&:join)
puts results
输出(示例):
下载 http://example.com: 200
下载 http://example.org: 200
下载 http://example.net: 200
说明:
- 使用
Queue
分配任务,线程安全。 Mutex
保护results
数组。pop(true)
非阻塞获取任务。
9. 总结
Ruby 的多线程通过 Thread
类实现,适合 I/O 密集型任务。Mutex
、ConditionVariable
和 Queue
提供线程同步机制,确保数据安全。受 GIL 限制,CPU 密集型任务需考虑 JRuby 或多进程。注意线程安全、异常处理和性能优化,复杂并发场景可使用 EventMachine
或 Sidekiq 等工具。
如果你需要更复杂的多线程示例(如线程池、并发爬虫)或有其他问题,请告诉我!