Condition variables and thread scheduling

A

Alex Young

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]?
 
R

Robert Klemme

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/
 
A

Alex Young

Robert Klemme wrote in post #964651:
Thread.start do
end
$ ruby a.rb
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.

That's what I thought, ok.
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:
<snip>

Perfect, thanks. I'll give that a try.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top