Mark Volkmann
1/10/2006 4:06:00 PM
Example code I've seen that uses a ConditionVariable seems to have a
timing flaw. For example, in Pickaxe 2, page 147 in the
Player/Customer example, the player thread does
plays_pending.wait_while { playlist.empty? }
This assumes that ok_to_shutdown gets set and detected in the previous
line before wait_while is called. If the timing works out such that
ok_to_shutdown doesn't get set to true until after wait_while is
called, I believe it will hang forever.
Isn't it the case that the customer thread could finish and the player
thread could reach the call to wait_while before ok_to_shutdown gets
set to true?
I think what needs to happen is what I show in the following example.
Note how the wait_while checks whether the producer thread is alive
and how I do a condition.signal at the bottom when I know the producer
thread has terminated ... which causes wait_while to evaluate the
condition again.
The call to sleep at the end of the producer thread is critical to
illustrating the problem. If I take that out then I can get lucky with
the timing and don't need to test whether the producer thread is alive
in wait_while.
Am I off my rocker? ;-)
require 'monitor'
#----------------------------------------------------------------------
class Widget
attr_accessor :id
def initialize(id)
self.id = id
end
def to_s
id
end
end
#----------------------------------------------------------------------
bin = []
bin.extend MonitorMixin
condition = bin.new_cond # type is MonitorMixin::ConditionVariable
#----------------------------------------------------------------------
producer = Thread.new do
widget_id = 0
5.times do
widget_id += 1
widget = Widget.new(widget_id)
puts "produced #{widget.to_s}"
bin.synchronize do
bin << widget
condition.signal # there is a widget to consume
end
end
# Delay termination of producer thread
# to demonstrate need for another condition.signal.
sleep 1
end
#----------------------------------------------------------------------
consumer = Thread.new do
finished = false
until (finished) do
bin.synchronize do
finished = (not producer.alive?) and bin.empty?
break if finished
# The condition will be tested once,
# and then again every time condition.signal is called.
condition.wait_while { producer.alive? and bin.empty? }
widget = bin.shift # removes first Widget from bin
puts "consumed #{widget.to_s}" if widget != nil
end
end
end
#----------------------------------------------------------------------
producer.join
bin.synchronize do
# Make consumer retest condition after producer terminates.
condition.signal
end
consumer.join
--
R. Mark Volkmann
Partner, Object Computing, Inc.