Ruby线程创建速度过快导致任务分配失效问题求解
问题根源
- 核心原因是Ruby for循环的变量作用域+闭包变量捕获特性导致的:Ruby中
for循环的迭代变量i属于外部作用域,整个循环过程复用同一个变量对象。你创建的所有线程的执行块都捕获引用了这同一个i变量,没有sleep时循环执行速度极快,远快于线程启动调度的速度,等所有线程真正开始执行worker(i)时,for循环已经跑完,i的值已经变成了最终的7,所以所有线程打印出来的线程号都是7,给你造成了「只有第7个线程在工作」的错觉,实际上所有线程都在正常消费队列,只是打印的编号都是7而已。 - 加了
sleep(0.1)能正常的原因是每次创建线程后等待的0.1秒足够操作系统调度启动新创建的线程,此时i还停留在当前循环的数值,所以每个线程拿到的编号是1到7的不同值,打印出来看起来就正常了。
更合理的解决方案
1. 修复变量捕获问题
把for循环替换为each迭代即可,each的块参数是每次迭代的局部变量,每个迭代的i是独立的对象,线程捕获的是当前迭代的独立值,不需要额外加sleep也能拿到正确的线程编号:
threads = [] (1..7).each do |i| threads << Thread.new { worker(i) } end
2. 优化队列消费逻辑避免竞态条件
你当前的worker写法while not @q.empty?再pop存在竞态风险:可能多个线程同时判断队列非空,随后别的线程先把最后一个元素取走,剩下的线程调用pop时会陷入永久阻塞。更安全的写法是给队列加入终止哨兵:
# 往队列塞完业务任务后,加入和线程数等量的终止哨兵 7.times { @q.push nil } # 修改worker逻辑,不需要判断队列是否为空 def worker(num) while (item = @q.pop) puts "Thread: #{num} Project: #{item}" sleep(0.5) end end
每个线程拿到nil哨兵就会自动退出,完全规避竞态问题。
内容的提问来源于stack exchange,提问作者mauam
相关产品推荐
相关产品推荐

