1

HTTP 応答時間を定期的にテストする必要がある URL でいっぱいのデータベースがあります。最近テストされていない URL に対して、常に多くのワーカー スレッドがデータベースを結合するようにしたいと考えています。URL が見つかった場合は、それをテストします。

もちろん、これにより、複数のスレッドがデータベースから同じ URL を取得する可能性があります。私はこれをしたくありません。そのため、これを防ぐためにミューテックスを使用しようとしています。データベース レベルには他のオプション (楽観的ロック、悲観的ロック) があることは認識していますが、少なくともこれが機能しない理由を理解したいと思います。

私が書いたこのテストコードを見てください:

threads = []
mutex = Mutex.new

50.times do |i|
  threads << Thread.new do
    while true do 
      url = nil

      mutex.synchronize do
        url = URL.first(:locked_for_testing => false, :times_tested.lt => 150)
        if url
          url.locked_for_testing = true
          url.save 
        end
      end

      if url
        # simulate testing the url
        sleep 1

        url.times_tested += 1
        url.save

        mutex.synchronize do
          url.locked_for_testing = false
          url.save
        end
      end
    end

    sleep 1
  end
end

threads.each { |t| t.join }

もちろん、実際の URL テストはありません。しかし、最終的には、各 URL の "times_tested" が 150 になるはずですよね?

(私は基本的に、ミューテックスとワーカースレッドの考え方が機能していることを確認しようとしています)

しかし、私がそれを実行するたびに、あちこちのいくつかの奇妙な URL は、times_tested がはるかに小さい数、たとえば 37 に等しくなり、locked_for_testing が「true」でフリーズします。

コードからわかる限り、いずれかの URL がロックされた場合は、ロックを解除する必要があります。そのため、一部の URL がそのように「凍結」してしまう理由がわかりません。

例外はなく、begin/ensure を追加しようとしましたが、何もしませんでした。

何か案は?

4

1 に答える 1

2

キューとマスターを使用して、必要なものをプルします。マスターが 1 つしかない場合は、何にアクセスするかを制御します。これは完璧ではありませんが、並行性のために爆発することはありません。データベースをロックしていない場合、ミューテックスは実際には役に立たないことを覚えておいてください。他の何かがデータベースにアクセスしています。

コードは完全にテストされていません

require 'thread'
queue = Queue.new
keep_running = true

# trap cntrl_c or something to reset keep_running
master = Thread.new do 
  while keep_running
    # check if we need some work to do
    if queue.size == 0
      urls = URL.all(:times_tested.lt => 150)
      urls.each do |u|
        queue << u.id
      end
      # keep from spinning the queue
      sleep(0.1)
    end
  end
end
workers = []
50.times do
  workers << Thread.new do
    while keep_running
      # get an id
      id = queue.shift
      url = URL.get(id)
      #do something with the url
      url.save
      sleep(0.1)
    end
  end
end
workers.each do |w|
  w.join
end
于 2012-10-30T03:54:04.020 に答える