我有一个充满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,但它没有做任何事情。
有什么想法吗?
答案 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