【问题标题】:Ruby work distribution fails if threads are generated to fast如果线程生成速度过快,Ruby 工作分配会失败
【发布时间】:2021-11-29 23:09:23
【问题描述】:

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

在此过程中,我将代码精简为以下版本。只要我在创建线程的循环中有sleep(0.1),这里的线程就可以工作。

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

有了这个“hack”,我确实有一个可行的解决方案,但我并不满意。我真的很好奇为什么会这样。

我在 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"

【问题讨论】:

    标签: ruby multithreading queue sleep worker


    【解决方案1】:

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

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

    试试这个工作函数,看看它是否不能清除问题

    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
    

    【讨论】:

    • 你说得对!在我的情况下,这并没有真正伤害,因为我只使用变量作为线程的标识符。然而,在其他情况下,这可能会产生很大的问题。
    • @mauam 我添加了一些解释,说明如果您愿意,您可以如何传递号码。
    • 谢谢。非常感激!鉴于“问题”,我认为这是一个直截了当的解决方案。我确实需要这些线程中的数字,我显然更喜欢上面的示例,而不是我在原始代码中使用的 sleep
    • @mauam 您也可以将for i in 1..7 do 更改为(1..7).each do |i|A for loop doesn’t create a new scope,所以 i 变量对于每次迭代都是相同的,但 each 确实创建了一个新范围,因此每次迭代 i var 是不同的。
    猜你喜欢
    • 1970-01-01
    • 2021-08-09
    • 1970-01-01
    • 2021-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 1970-01-01
    相关资源
    最近更新 更多