如果线程生成得太快,Ruby工作分发就会失败



前几天我遇到了一个问题,我花了两个小时在错误的地方寻找答案。

在这个过程中,我将代码剥离到下面的版本。只要我在循环中有创建线程的sleep(0.1),这里的Threading就可以工作。

如果省略这行,则创建所有线程-但只有线程7实际使用队列中的数据。

有了这个"黑客"我确实有一个可行的解决方案,但我并不满意。我真的很好奇为什么会发生这种情况。

我在windows 2.4.1p111下使用一个相当旧的ruby版本。然而,我能够在新的ruby 3.0.2p107安装中重现相同的行为

#!/usr/bin/env ruby
@q = Queue.new

# Get all projects (would be a list of directories)
projects = [*0..100]
projects.each do |project|
@q.push project
end
def worker(num)
while not @q.empty?
puts "Thread: #{num} Project: #{@q.pop}"
sleep(0.5)
end
end 

threads=[]
for i in 1..7 do
threads << Thread.new { worker(i) }
sleep(0.1) # Threading does not work without this line - but why?
end
threads.each {|thread| puts thread.join }
puts "done"

有趣的bug!这是一个竞争条件。

并不是只有线程7在做工作,而是所有线程都在内存中引用相同的变量i(只有一个副本!最后写入数字7(假设在任何线程启动之前),它们都读取相同的i==7

试试这个worker函数,看看它是否还没有把事情弄清楚

def worker(num)
my_thread_id = Thread.current.object_id
while not @q.empty?
puts "Thread: #{num} NumObjId: #{num.object_id} ThreadId: #{my_thread_id} Project: #{@q.pop}"
sleep(0.5)
end
end

注意,NumObjId在所有线程中都是相同的。它们都指向同一个数。但是实际得到的ThreadId是不同的。

如果您确实需要每个线程中的数字分配尽可能多的数字作为线程。就像

ids = (1..7).to_a
ids.each do |i|
threads << Thread.new { worker(i) }
end

最新更新