1

我有一个充满 URL 的数据库,我需要定期测试 HTTP 响应时间。我希望有许多工作线程始终为最近未测试的 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 最终都以 time_tested 等于一个低得多的数字,比如 37,并且locked_for_testing 冻结为“true”

现在,据我从代码中可以看出,如果任何 URL 被锁定,则必须解锁。所以我不明白一些 URL 是如何像那样“冻结”的。

没有例外,我尝试添加开始/确保,但它没有做任何事情。

有任何想法吗?

4

1 回答 1

2

我会使用一个队列和一个主人来拉你想要的东西。如果您有一个主控,您可以控制访问的内容。这并不完美,但它不会因为并发性而崩溃,请记住,如果您没有锁定数据库,互斥锁并不能真正帮助您,而是访问数据库的其他东西。

完全未经测试的代码

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 回答