On Mon, Nov 29, 2010 at 10:51 AM, Alex Young <alex / blackkettle.org> 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
> =A0a =3D ""
> =A0a.extend(MonitorMixin)
>
> =A0cvar =3D a.new_cond
>
> =A0t1 =3D Thread.start do
> =A0 =A0a.synchronize do
> =A0 =A0 =A0cvar.wait
> =A0 =A0 =A0a << "b"
> =A0 =A0end
> =A0end
>
> =A0t2,t3 =3D [2,3].map{
> =A0 =A0Thread.start do
> =A0 =A0 =A0a.synchronize do
> =A0 =A0 =A0 =A0a << "a"
> =A0 =A0 =A0 =A0cvar.signal
> =A0 =A0 =A0end
> =A0 =A0end
> =A0}
>
> =A0[t2,t3,t1].each{|t| t.join}
>
> =A0return a
> end
>
>
> 1000.times do |i|
> =A0output =3D try()
> =A0raise "Oops! (#{i} says #{output})" unless output =3D=3D "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)
> =A0from a.rb:37:in `times'
> =A0from 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 =3D true

def try
  a =3D ""
  a.extend(MonitorMixin)
  next_run =3D :b

  cond_a =3D a.new_cond
  cond_b =3D a.new_cond

  t1 =3D Thread.start do
    a.synchronize do
      until next_run =3D=3D :a
        cond_a.wait
      end

      a << "b"

      next_run =3D :b
      cond_b.signal
    end
  end

  t2,t3 =3D [2,3].map do
    Thread.start do
      a.synchronize do
        until next_run =3D=3D :b
          cond_b.wait
        end

        a << "a"

        next_run =3D :a
        cond_a.signal
      end
    end
  end

  [t1,t2,t3].each{|t| t.join}

  return a
end


1000.times do |i|
  output =3D try()
  raise "Oops! (#{i} says #{output})" unless output =3D=3D "aba"
end

Kind regards

robert


--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/