4

Rubyの条件変数に関するリソースはそれほど多くありませんが、ほとんどのリソースは間違っています。ruby-docのように、ここにチュートリアルまたはここに投稿してください-それらはすべてデッドロックの可能性に苦しんでいます。

スレッドを指定された順序で開始sleepし、同期を強制するために間にスレッドを配置することで、問題を解決できます。しかし、それは本当の問題を延期しているだけです。

私はコードを古典的な生産者/消費者問題に書き直しました:

require 'thread'
queue = []
mutex = Mutex.new
resource = ConditionVariable.new
threads = []

threads << Thread.new do
  5.times do |i|
    mutex.synchronize do 
      resource.wait(mutex)
      value = queue.pop
      print "consumed #{value}\n"
    end
  end
end

threads << Thread.new do
  5.times do |i|
    mutex.synchronize do
      queue << i
      print "#{i} produced\n"
      resource.signal
    end
    sleep(1) #simulate expense
  end
end

threads.each(&:join)

時々あなたはこれを得るでしょう(しかし常にではありません):

0 produced
1 produced
consumed 0
2 produced
consumed 1
3 produced
consumed 2
4 produced
consumed 3
producer-consumer.rb:30:in `join': deadlock detected (fatal)
        from producer-consumer.rb:30:in `each'
        from producer-consumer.rb:30:in `<main>'

正しい解決策は何ですか?

4

4 に答える 4

1

これは、複数のコンシューマーとプロデューサー、およびMonitorMixinの使用を伴う、より堅牢なソリューションでMonitorMixinあり、特別なConditionVariablewithwait_while()およびwait_until()メソッドがあります。

require 'monitor'

queue = []
queue.extend(MonitorMixin)
cond = queue.new_cond
consumers, producers = [], []

for i in 0..5
  consumers << Thread.start(i) do |i|
      print "consumer start #{i}\n"
      while (producers.any?(&:alive?) || !queue.empty?)
        queue.synchronize do
        cond.wait_while { queue.empty? }
        print "consumer #{i}: #{queue.shift}\n"
      end
      sleep(0.2) #simulate expense
    end
  end
end

for i in 0..3
  producers << Thread.start(i) do |i|
    id = (65+i).chr
    for j in 0..10 do
      queue.synchronize do
        item = "#{j} #{id}"
        queue << item
        print "producer #{id}: produced #{item}\n"
        j += 1
        cond.broadcast
      end
      sleep(0.1) #simulate expense
    end
  end
end

sleep 0.1 while producers.any?(&:alive?)
sleep 0.1 while consumers.any?(&:alive?)

print "queue size #{queue.size}\n"
于 2012-10-26T14:50:50.360 に答える
0

フォーラムのスレッドに基づいて、実用的なソリューションを思いつきました。理想的ではないスレッド間の交代を強制します。消費者と生産者の複数のスレッドが必要なのは何ですか?

queue = []
mutex = Mutex.new
threads = []

next_run = :producer

cond_consumer = ConditionVariable.new
cond_producer = ConditionVariable.new

threads << Thread.new do
  5.times do |i|
    mutex.synchronize do
      until next_run == :consumer
        cond_consumer.wait(mutex)
      end

      value = queue.pop
      print "consumed #{value}\n"
      next_run = :producer
      cond_producer.signal
    end
  end
end

threads << Thread.new do
  5.times do |i|
    mutex.synchronize do
      until next_run == :producer
          cond_producer.wait(mutex)
      end
      queue << i
      print "#{i} produced\n"
      next_run = :consumer
      cond_consumer.signal
    end
  end
end

threads.each(&:join)
于 2012-10-26T11:37:06.067 に答える
0

問題を単純化できます。

require 'thread'
queue = Queue.new
consumer = Thread.new { queue.pop }
consumer.join

メイン スレッドはコンシューマー スレッドが終了するのを待っていますが、コンシューマー スレッドは (原因でqueue.pop) スリープしているため、次の結果になります。

producer-consumer.rb:4:in `join': deadlock detected (fatal)
    from producer-consumer.rb:4:in `<main>'

したがって、スレッドが終了するのを待たなければなりませんjoin

require 'thread'

queue = Queue.new
threads = []

threads << Thread.new do
  5.times do |i|
    value = queue.pop
    puts "consumed #{value}"
  end
end

threads << Thread.new do
  5.times do |i|
    queue << i
    puts "#{i} produced"
    sleep(1) # simulate expense
  end
end

# wait for the threads to finish
sleep(1) while threads.any?(&:alive?)
于 2012-10-26T13:03:01.173 に答える