Again some problem with my multithreaded teadrinker app

T

Tassilo Horn

Hi,

I still have some problems with my teadrinker app. After I implemented
the things SER told me in

Message-ID: <[email protected]>

the program works very good for 3 drinkers.

Now I wanted it to support more than 3 drinker Threads.

Here's the code:

<----- CODE ----->
#! /usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
drinker_cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []
items = %w{ Cup Water Tea }

# min. 3 drinkers.
if ARGV[0].to_i >= 3
drinkers_count = ARGV[0].to_i
else
drinkers_count = 3
end

drinkers_count.times do |i|
case i%3
when 0
# this drinker has water and tea
needs = [1,2]
when 1
# this drinker has a cup and tea
needs = [0,2]
when 2
# this drinker has a cup and water
needs = [0,1]
end
Thread.new {
puts "Starting Drinker#{i}"
mutex.synchronize {
while true
drinker_cv.wait(mutex)
# The things on the table are the things the waiter needs.
if needs.sort == table.sort
puts "Drinker#{i}: #{items[table[0]]} and
#{items[table[1]]} on table. I'll cook my tea!"
# The Drinker took the items from the table. Now it's empty
# till the waiter puts new items on it.
table = []
sleep 1 # (1) #
puts "Drinker#{i}: Drinking my tea. I call the waiter again."
# Wake up the waiter Thread
waiter_cv.signal
sleep 1 # (2) #
puts "Drinker#{i}: Now I'll read my newspaper."
end
end
}
}
end

waiter = Thread.new {
mutex.synchronize {
while true
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
#sleep 2
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
$stdout << "WAITER: Putting " << table[0]
<< " and " << table[1] << " on the table.\n"
else
puts "WAITER: Why did you call me???"
end
# Call waiting tea drinkers.
drinker_cv.broadcast # (3) #
puts "WAITER: Waiting for new appointments..."
waiter_cv.wait(mutex)
end
}
}

waiter.join
<----- END_OF_CODE ----->

If I start it with let's say 20 drinkers (./TeaRoom.rb 20) it starts all
threads, but only the drinkers 0..2 cook their tea. If the table
contains the items 1 and 2 always drinker0 will cook his tea although
drinker3, drinker6, drinker9 etc need the same items.

Why is it the way it is and how do I get it the way I want which means
that the probability of using drinkerX is the same as using drinkerX+-3?
Why does drinker_cv.broadcast (line marked with '# (3) #') always wake
up the same threads?

One interesting this is that if I remove the lines '# (2) #' and '# (2)
#' (the sleep() method calls) more drinkers get their turn. But still
there are drinkers who never drink a tea and others who extremely often
have to drink.

Can anybody help me before any of my tearoom customers die with thirst
or because of too much tea?

Thanks,
Tassilo
 
M

Markus

I'd suggested this before (as the solution to your first problem) but it
seemed to have gotten missed--the nesting of your looping/locking needs
to be swapped. In the present case, reversing both of the "
mutex.synchronize"/"while true" pairs (and their associated "}"/"end"
pairs) should fix the problem.

-- Markus

P.S. If the reason isn't clear, ask yourself when/under what condition
(in the code as posted) did it ever leave the synchronize block? If it
still isn't clear after pondering that, post again & I'll give you a
more detailed answer.


Hi,

I still have some problems with my teadrinker app. After I implemented
the things SER told me in

Message-ID: <[email protected]>

the program works very good for 3 drinkers.

Now I wanted it to support more than 3 drinker Threads.

Here's the code:

<----- CODE ----->
#! /usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
drinker_cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []
items = %w{ Cup Water Tea }

# min. 3 drinkers.
if ARGV[0].to_i >= 3
drinkers_count = ARGV[0].to_i
else
drinkers_count = 3
end

drinkers_count.times do |i|
case i%3
when 0
# this drinker has water and tea
needs = [1,2]
when 1
# this drinker has a cup and tea
needs = [0,2]
when 2
# this drinker has a cup and water
needs = [0,1]
end
Thread.new {
puts "Starting Drinker#{i}"
mutex.synchronize {
while true
drinker_cv.wait(mutex)
# The things on the table are the things the waiter needs.
if needs.sort == table.sort
puts "Drinker#{i}: #{items[table[0]]} and
#{items[table[1]]} on table. I'll cook my tea!"
# The Drinker took the items from the table. Now it's empty
# till the waiter puts new items on it.
table = []
sleep 1 # (1) #
puts "Drinker#{i}: Drinking my tea. I call the waiter again."
# Wake up the waiter Thread
waiter_cv.signal
sleep 1 # (2) #
puts "Drinker#{i}: Now I'll read my newspaper."
end
end
}
}
end

waiter = Thread.new {
mutex.synchronize {
while true
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
#sleep 2
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
$stdout << "WAITER: Putting " << table[0]
<< " and " << table[1] << " on the table.\n"
else
puts "WAITER: Why did you call me???"
end
# Call waiting tea drinkers.
drinker_cv.broadcast # (3) #
puts "WAITER: Waiting for new appointments..."
waiter_cv.wait(mutex)
end
}
}

waiter.join
<----- END_OF_CODE ----->

If I start it with let's say 20 drinkers (./TeaRoom.rb 20) it starts all
threads, but only the drinkers 0..2 cook their tea. If the table
contains the items 1 and 2 always drinker0 will cook his tea although
drinker3, drinker6, drinker9 etc need the same items.

Why is it the way it is and how do I get it the way I want which means
that the probability of using drinkerX is the same as using drinkerX+-3?
Why does drinker_cv.broadcast (line marked with '# (3) #') always wake
up the same threads?

One interesting this is that if I remove the lines '# (2) #' and '# (2)
#' (the sleep() method calls) more drinkers get their turn. But still
there are drinkers who never drink a tea and others who extremely often
have to drink.

Can anybody help me before any of my tearoom customers die with thirst
or because of too much tea?

Thanks,
Tassilo
 
T

Tassilo Horn

Markus said:
I'd suggested this before (as the solution to your first problem) but it
seemed to have gotten missed--the nesting of your looping/locking needs
to be swapped. In the present case, reversing both of the "
mutex.synchronize"/"while true" pairs (and their associated "}"/"end"
pairs) should fix the problem.

It seems to work if I start many threads, but if I only use three I get
an deadlock after one or two drinkers had their tea:

(%:~/tmp/teadrinkers--mainline--0.3--patch-1)- ./TeaRoom.rb 3
Starting Drinker0
Starting Drinker1
Starting Drinker2
WAITER: Looking at the table!
WAITER: Oh, nothing on the table!
WAITER: Putting 2 and 1 on the table.
WAITER: Waiting for new appointments...
Drinker0: Tea and Water on table. I'll cook my tea!
Drinker0: Drinking my tea. I call the waiter again.
Drinker0: Now I'll read my newspaper.
WAITER: Looking at the table!
WAITER: Oh, nothing on the table!
WAITER: Putting 0 and 1 on the table.
WAITER: Waiting for new appointments...
Drinker2: Cup and Water on table. I'll cook my tea!
Drinker2: Drinking my tea. I call the waiter again.
Drinker2: Now I'll read my newspaper.
WAITER: Looking at the table!
WAITER: Oh, nothing on the table!
WAITER: Putting 1 and 0 on the table.
WAITER: Waiting for new appointments...
deadlock 0x4022b0dc: sleep:- - /usr/lib/ruby/1.8/thread.rb:195
deadlock 0x40240798: sleep:J(0x4022acb8) (main) - ./TeaRoom.rb:81
deadlock 0x4022acb8: sleep:- - /usr/lib/ruby/1.8/thread.rb:195
deadlock 0x4022ade4: sleep:- - /usr/lib/ruby/1.8/thread.rb:195
deadlock 0x4022af60: sleep:- - /usr/lib/ruby/1.8/thread.rb:195
/usr/lib/ruby/1.8/thread.rb:195: Thread(0x4022af60): deadlock (fatal)
P.S. If the reason isn't clear, ask yourself when/under what condition
(in the code as posted) did it ever leave the synchronize block?

Never. And when the waiter calls drinker_cv.broadcast() always the
thread who entered the mutex.synchronize-block first and obtained the
lock first is woken up. Right?

Thanks for your help,
Tassilo
 
M

Markus

Odd. I'll look at it some more this evening (though as I'm leaving for
a week in Costa Rica this Sunday my time is growing short).

Does it always deadlock on in same state (0 & 1 on the table)? Or is
there any other pattern you can discern?

-- Markus

P.S. Your understanding of the fix is spot on.
 
T

Tassilo Horn

Tassilo Horn said:
Yes, really. It deadlocks always when the waiter puts 0 and 1 (or 1 and
0) on the table...

Forget it. I tested this five times and it always crashed with 0 and 1
on the table, but this was a coincidence. After some further testing I
can't find any connection between the things on the table and the
crashes.

Regards,
Tassilo
 
M

Markus

I think (and this late in a long day for me, so I may be wrong)
that the problem is that you are using broadcast to wake the drinkers.
This causes a race condition (as they all assess the table contents "at
once") and an eventual deadlock.

I tried a quick test of 1) replacing the broadcast with a signal,
and 2) adding an else-clause so that the drinker re-signals if what he
finds on the table is not what he's wanting.

It makes sense & seems to work, but I'm too pooped to vouch for it
beyond that.

-- Markus
 
M

Markus

--=-051zEIgjMhjMWwe+h154
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

It still works for me. See attached. Diff it perhaps?

-- Markus

This has come in my mind, too, but...


....if I do exactly this it deadlocks even faster.


Hm, it makes sense to me, too. But how can it be that it works for you
but not for me? I use ruby-1.8.2_pre2.


Thanks and regards,

Tassilo

--=-051zEIgjMhjMWwe+h154
Content-Disposition: attachment; filename=waiter
Content-Type: text/plain; name=waiter; charset=UTF-8
Content-Transfer-Encoding: 7bit

#! /usr/bin/env ruby
require 'thread'

# For synchronisation
mutex = Mutex.new
drinker_cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []
items = %w{ Cup Water Tea }

# min. 3 drinkers.
if ARGV[0].to_i >= 3
drinkers_count = ARGV[0].to_i
else
drinkers_count = 3
end

drinkers_count.times do |i|
case i%3
when 0
# this drinker has water and tea
needs = [1,2]
when 1
# this drinker has a cup and tea
needs = [0,2]
when 2
# this drinker has a cup and water
needs = [0,1]
end
Thread.new {
puts "Starting Drinker#{i}"
while true
mutex.synchronize {
drinker_cv.wait(mutex)
# The things on the table are the things the waiter needs.
if needs.sort == table.sort
puts "Drinker#{i}: #{items[table[0]]} and #{items[table[1]]} on table. I'll cook my tea!"
# The Drinker took the items from the table. Now it's empty
# till the waiter puts new items on it.
table = []
##sleep 1 # (1) #
puts "Drinker#{i}: Drinking my tea. I call the waiter again."
# Wake up the waiter Thread
waiter_cv.signal
##sleep 1 # (2) #
puts "Drinker#{i}: Now I'll read my newspaper."
else
drinker_cv.signal
end
}
end
}
end

waiter = Thread.new {
while true
mutex.synchronize {
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
#sleep 2
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
$stdout << "WAITER: Putting " << table[0] << " and " << table[1] << " on the table.\n"
else
puts "WAITER: Why did you call me???"
end
# Call waiting tea drinkers.
drinker_cv.signal # (3) #
puts "WAITER: Waiting for new appointments..."
waiter_cv.wait(mutex)
}
end
}

waiter.join
--=-051zEIgjMhjMWwe+h154--
 
T

Tassilo Horn

Markus said:
It still works for me. See attached. Diff it perhaps?

I had a typo (dinker_cv.signal instead of drinker_cv.signal) in the
drinker's else clause.

And that's one thing I don't like with Ruby. Why doesn't it tell me that
I don't have a variable dinker_cv?

irb does tell me that:

,----
| irb(main):002:0> dinker_cv.signal
| NameError: undefined local variable or method `dinker_cv' for main:Object
| from (irb):2
`----

Thanks a lot for your help,

Tassilo
 
M

Markus

IIRC correctly, you normally would have gotten a similar message (or one
about nil not supporting the method) but it happened in a thread and you
had Thread.abort_on_exception set to false (the default), so it got
swallowed.

-- Markus
 
T

Tassilo Horn

Markus said:
IIRC correctly, you normally would have gotten a similar message (or one
about nil not supporting the method) but it happened in a thread and you
had Thread.abort_on_exception set to false (the default), so it got
swallowed.

Ah, thanks. After setting Thread.abort_on_exception = true I get

,----
| WAITER: Waiting for new appointments...
| ./TeaRoom.rb:56: undefined local variable or method `dinker_cv' for
| main:Object(NameError)
`----

I should have known this before. These little typing errors made me
debug my code for much too much time.

Thank you,
Tassilo
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top