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#joinThread#value 捕获异常。

6. GIL 的影响

Ruby MRI(Matz’s Ruby Interpreter)中的 GIL 限制了多线程的 CPU 并行性:

  • I/O 密集型任务:如网络请求、文件读写,线程切换有效,性能良好。
  • CPU 密集型任务:如复杂计算,受 GIL 限制,无法利用多核。推荐:
  • 使用 JRuby 或 Rubinius(无 GIL)。
  • 使用多进程(Process.forkParallel gem)。

7. 注意事项

  • 线程安全
  • 使用 MutexQueue 避免数据竞争。
  • 避免修改共享对象(如数组、哈希)而不加锁。
  • 性能
  • 线程切换有开销,过多线程可能降低效率。
  • 使用线程池(如 Thread.pool gem)限制线程数。
  • 调试
  • 使用 Thread.list 检查活跃线程。
  • 记录线程 ID(Thread.current.object_id)跟踪执行。
  • 异常:确保每个线程的异常被捕获,否则可能导致未预期的终止。
  • 替代方案
  • 异步库:如 EventMachineAsync gem。
  • 多进程:使用 ParallelFork 处理 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 密集型任务。MutexConditionVariableQueue 提供线程同步机制,确保数据安全。受 GIL 限制,CPU 密集型任务需考虑 JRuby 或多进程。注意线程安全、异常处理和性能优化,复杂并发场景可使用 EventMachine 或 Sidekiq 等工具。

如果你需要更复杂的多线程示例(如线程池、并发爬虫)或有其他问题,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注