[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Again some problem with my multithreaded teadrinker app

Tassilo Horn

10/15/2004 12:36:00 PM

Hi,

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

Message-ID: <1097253853.581059.173460@z14g2000cwz.googlegroups.com>

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
11 Answers

Markus

10/15/2004 4:28:00 PM

0

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.


On Fri, 2004-10-15 at 05:39, Tassilo Horn wrote:
> Hi,
>
> I still have some problems with my teadrinker app. After I implemented
> the things SER told me in
>
> Message-ID: <1097253853.581059.173460@z14g2000cwz.googlegroups.com>
>
> 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



Tassilo Horn

10/15/2004 5:46:00 PM

0

Markus <markus@reality.com> writes:

> 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
--
"At least they're........EXPERIENCED incompetents"

Markus

10/15/2004 6:00:00 PM

0

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.

On Fri, 2004-10-15 at 10:49, Tassilo Horn wrote:
> Markus <markus@reality.com> writes:
>
> > 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



Tassilo Horn

10/15/2004 6:20:00 PM

0

Markus <markus@reality.com> writes:

> 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).

Oh, have a nice trip.

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

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

> -- Markus
>
> P.S. Your understanding of the fix is spot on.

Good, then I think "the Grosche is gefalle". ;-)

Regards,
Tassilo
--
Q: What's the difference between a golfer and a skydiver?
A: A golfer goes "[WHACK] ... Oh shit!". /---------------\
A skydiver goes "Oh shit! ... [WHACK]" | www.fscnrw.de |
\---------------/

Tassilo Horn

10/15/2004 6:57:00 PM

0

"Tassilo Horn" <heimdall@uni-koblenz.de> writes:

> 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
--
echo '[dO%O+38%O+PO/d0<0]Fi22os0CC4BA64E418CE7l0xAP'|dc

Markus

10/16/2004 5:08:00 AM

0

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





On Fri, 2004-10-15 at 10:49, Tassilo Horn wrote:
> Markus <markus@reality.com> writes:
>
> > 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



Tassilo Horn

10/16/2004 3:03:00 PM

0

Markus <markus@reality.com> writes:

> 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.

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

> 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.

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

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

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.

> -- Markus

Thanks and regards,

Tassilo

Markus

10/16/2004 3:55:00 PM

0

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

-- Markus

On Sat, 2004-10-16 at 08:04, Tassilo Horn wrote:
> Markus <markus@reality.com> writes:
>
> > 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.
>
> This has come in my mind, too, but...
>
> > 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.
>
> ....if I do exactly this it deadlocks even faster.
>
> > It makes sense & seems to work, but I'm too pooped to vouch for it
> > beyond that.
>
> 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.
>
> > -- Markus
>
> Thanks and regards,
>
> Tassilo

Tassilo Horn

10/16/2004 7:19:00 PM

0

Markus <markus@reality.com> writes:

> 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

Markus

10/16/2004 7:40:00 PM

0

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


On Sat, 2004-10-16 at 12:19, Tassilo Horn wrote:
> Markus <markus@reality.com> writes:
>
> > 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