From: Robert Klemme Date: 2010-11-29T19:15:42+09:00 Subject: Re: Condition variables and thread scheduling On Mon, Nov 29, 2010 at 10:51 AM, Alex Young wrote: > I'm trying to understand how condition variables are supposed to work; > specifically, I've got this code: > > > # a.rb > require 'monitor' > > def try >  a = "" >  a.extend(MonitorMixin) > >  cvar = a.new_cond > >  t1 = Thread.start do >    a.synchronize do >      cvar.wait >      a << "b" >    end >  end > >  t2,t3 = [2,3].map{ >    Thread.start do >      a.synchronize do >        a << "a" >        cvar.signal >      end >    end >  } > >  [t2,t3,t1].each{|t| t.join} > >  return a > end > > > 1000.times do |i| >  output = try() >  raise "Oops! (#{i} says #{output})" unless output == "aba" > end > > > This consistently fails like this on both jruby-1.5.1 and ruby-1.8.7: > > $ ruby a.rb > a.rb:39: Oops! (220 says aab) (RuntimeError) >  from a.rb:37:in `times' >  from a.rb:37 > > That means that t1 was woken up after both t2 and t3, despite being > signaled to wake up by the first to execute, and it has no indication > that it was signaled twice. > > I presume this is intentional behaviour (although it's a *little* > surprising), and the same thing happens if I use Mutex and > ConditionVariable instead of MonitorMixin, so is there any way to ensure > that the thread scheduling goes as [t2,t1,t3] *or* [t3,t1,t2], but never > [t2,t3,t1]? Yes, this is intentional behavior. There are no guarantees with regard to timing and order in which threads obtain the monitor. This means especially that thread 2 and 3 can obtain any number of times before thread 1 runs again. Please note also that the intended usage of condition variables is different: you obtain a lock then you loop while the condition is not reached and then you continue. This is what you rather want if you want to ensure alternation: require 'monitor' Thread.abort_on_exception = true def try a = "" a.extend(MonitorMixin) next_run = :b cond_a = a.new_cond cond_b = a.new_cond t1 = Thread.start do a.synchronize do until next_run == :a cond_a.wait end a << "b" next_run = :b cond_b.signal end end t2,t3 = [2,3].map do Thread.start do a.synchronize do until next_run == :b cond_b.wait end a << "a" next_run = :a cond_a.signal end end end [t1,t2,t3].each{|t| t.join} return a end 1000.times do |i| output = try() raise "Oops! (#{i} says #{output})" unless output == "aba" end Kind regards robert -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/