我正在运行一些代码(经过简化,但仍在下面破坏版本),该代码在〜1/3000次执行中等待第一个开关失败。应该发生的是:
threads[0]
启动并获取互斥锁threads[0]
通知cond_main
,以便主线程可以创建thread[1]
thread[1]
/ thread[0]
做一些工作,等待对方的信号不幸的是,它在thread[0]
中失败-cond.wait
以超时结束并引发异常。我该如何同步它,以确保cond_main
不会太早收到信号?
理想情况下,我想从主线程传递一个锁定的互斥锁,然后在第一个生成的线程中将其解锁,但是Ruby要求在同一线程中解锁互斥锁-因此这是行不通的。
自包含的复制器(本身并没有多大意义,但实际的工作被剥离了):
def run_test
mutex = Mutex.new
cond = ConditionVariable.new
cond_main = ConditionVariable.new
threads = []
t1_done = false
t2_done = false
threads << Thread.new do
mutex.synchronize do
# this needs to happen first
cond_main.signal
cond.wait(mutex, 2)
raise 'timeout waiting for switch' if !t2_done
# some work
t1_done = true
cond.signal
end
end
cond_main.wait(Mutex.new.lock, 2)
threads << Thread.new do
mutex.synchronize do
cond.signal
# some work
t2_done = true
cond.wait(mutex, 2)
raise 'timeout waiting for switch' if !t1_done
end
end
threads.map(&:join)
end
5000.times { |x|
puts "Run #{x}"
run_test
}
在Ruby 2.5.3上测试
答案 0 :(得分:1)
设置一个while块,以在第二个线程完成时停止等待(请参见更多here):
def run_test
mutex = Mutex.new
cond = ConditionVariable.new
cond_main = ConditionVariable.new
threads = []
spawned = false
t1_done = false
t2_done = false
threads << Thread.new do
mutex.synchronize do
while(!spawned) do
cond.wait(mutex, 2)
end
raise 'timeout waiting for switch' if !t2_done
# some work
t1_done = true
cond.signal
end
end
threads << Thread.new do
mutex.synchronize do
spawned = true
cond.signal
# some work
t2_done = true
cond.wait(mutex, 2)
raise 'timeout waiting for switch' if !t1_done
end
end
threads.map(&:join)
end
50000.times { |x|
puts x
run_test
}
或者,使用counting semaphore,我们可以为线程分配一些优先级:
require 'concurrent-ruby'
def run_test
mutex = Mutex.new
sync = Concurrent::Semaphore.new(0)
cond = ConditionVariable.new
cond_main = ConditionVariable.new
threads = []
t1_done = false
t2_done = false
threads << Thread.new do
mutex.synchronize do
sync.release(1)
# this needs to happen first
cond.wait(mutex, 2)
raise 'timeout waiting for switch' if !t2_done
# some work
t1_done = true
cond.signal
end
end
threads << Thread.new do
sync.acquire(1)
mutex.synchronize do
cond.signal
# some work
t2_done = true
cond.wait(mutex, 2)
raise 'timeout waiting for switch' if !t1_done
end
end
threads.map(&:join)
end
50000.times { |x|
puts x
run_test
}
我更喜欢第二种解决方案,因为它可以让您控制线程的顺序,尽管感觉有点脏。
出于好奇,在Ruby 2.6上,您的代码似乎没有引发异常(经测试,运行次数超过10M)。