DataMapper,Mutexes和Threads的奇怪错误?

时间:2012-08-19 02:41:40

标签: ruby multithreading concurrency locking datamapper

我有一个充满URL的数据库,我需要定期测试HTTP响应时间。我希望有许多工作线程一直在为数据库梳理最近未经过测试的URL,如果找到了,请测试它。

当然,这可能会导致多个线程从数据库中获取相同的URL。我不想要这个。所以,我正在尝试使用Mutexes来防止这种情况发生。我意识到在数据库级别还有其他选项(乐观锁定,悲观锁定),但我至少更愿意弄清楚为什么这不起作用。

看看我写的这个测试代码:

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”上

现在,就我的代码而言,如果任何网址被锁定,它将 解锁。所以我不明白一些网址是如何“冻结”的那样。

没有例外,我尝试添加begin / ensure,但它没有做任何事情。

有什么想法吗?

1 个答案:

答案 0 :(得分: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