问题
I have method a
that is invoked repeatedly at some random time, which triggers method b
, which is completely executed after some random time and is in it own thread. I want to ensure that a subsequent execution of a
waits until b
is completed, which is triggered by the current execution of a
. In other words, a
and b
are to be executed alternatively. I tried to do this using mutex and condition variable as follows:
def a
Thread.new do
$mutex.synchronize do
puts "a"
b
$cv.wait($mutex)
end
end
end
def b
Thread.new do
sleep(rand)
$mutex.synchronize do
puts "b"
$cv.signal
end
end
end
$mutex, $cv = Mutex.new, ConditionVariable.new
loop{a; sleep(rand)}
In this code, $mutex.synchronize do ... end
in method a
ensures that $cv.signal
(also within another $mutex.synchronize do ... end
) in method b
is not invoked until $cv.wait($mutex)
sets $cv
into listening mode for signals. This much is given in the document.
Another function I intended to assign to $mutex.synchronize do ... end
in method a
is to avoid consecutive execution of method a
. My reasoning is that $cv.wait($mutex)
in method a
should avoid $mutex
from being completed and released until $cv.signal
in method b
is invoked, by which time b
should be finished.
I expected that a
and b
are executed alternatively, thereby printing "a"
and "b"
alternatively. But in reality, they are not; each of "a"
or "b"
can be printed consecutively.
After that, I thought that my reasoning above may be wrong in the sense that $mutex
is rather completed and released even if $cv
(or $mutex
) is in waiting mode, once $cv.wait($mutex)
has been called. So I added some dummy process to a
, changing it to:
def a
Thread.new do
$mutex.synchronize do
puts "a"
b
$cv.wait($mutex)
nil # Dummy process intended to keep `$mutex` locked until `$cv` is released
end
end
end
but that did not have effect.
How can this be fixed? Or, what am I wrong about this?
回答1:
I know it sounds strange, but it will be easier to use a queue to block those threads:
def a
Thread.new do
$queue.pop
puts "a"
b
end
end
def b
Thread.new do
sleep(rand)
puts "b"
$queue << true
end
end
$queue = Queue.new
$queue << true
loop{a; sleep(rand)}
回答2:
I don't have a solution for you, but isn't the reason a
is being called more than you expect is wait
releases the lock on the mutex? Otherwise signal
could never be called. This seems to happen "as expected" the first time, but after that, you end up having several a
threads queued up, itching to enter the synchronize
block, and they sneak in before a b
thread wakes up and locks the mutex again.
If you poor-man's instrument your code at every turn, you can see it happen:
def a
puts("a before thread #{Thread.current}")
Thread.new do
puts(" a synch0 #{Thread.current}")
$mutex.synchronize do
puts(" a before b #{Thread.current}")
b
puts(" a after b, before wait #{Thread.current}")
$cv.wait($mutex)
puts(" a after wait #{Thread.current}")
end
puts(" a synch1 #{Thread.current}")
end
puts("a after thread #{Thread.current}")
end
def b
puts("b before thread #{Thread.current}")
Thread.new do
puts(" b before sleep #{Thread.current}")
sleep(rand)
puts(" b after sleep, synch0 #{Thread.current}")
$mutex.synchronize do
puts(" b before signal #{Thread.current}")
$cv.signal
puts(" b after signal #{Thread.current}")
end
puts(" b synch1 #{Thread.current}")
end
puts("b after thread #{Thread.current}")
end
$mutex, $cv = Mutex.new, ConditionVariable.new
loop{a; sleep(rand)}
来源:https://stackoverflow.com/questions/23668259/alternating-routines-sharing-a-mutex