Ruby Multithreaded producer-consumer problem

Hi,
I started on Ruby less than a week ago but have already come to
appreciate the power of the language. I am trying my hands on a classic
producer-consumer problem, implemented as an Orange tree (c.f.
http://pine.fm/LearnToProgram/?Chapter=09). The Orange tree grows each
year until it dies and produces a random number of Oranges each year
(Producer). Oranges can be picked as long there are any on the tree
(Consumer).

I’ve got two problems here:

  1. The attached code gives me the following exception:
    /Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:84:
    warning: instance variable @orange_tree not initialized
    /Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:84:in
    <class:Worker>': undefined method age’ for nil:NilClass
    (NoMethodError)
    from
    /Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:45:in
    `’

  2. I am not sure that the multithreading part is correctly coded.

Any help is very much appreciated. I’ve got myself a couple of books,
including “Programming Ruby” and “The Ruby P.ming Language” but
none of them contain a true “producer-consumer problem”.

You might take a look at Celluloid:

Subject: Ruby Multithreaded producer-consumer problem
Date: gio 14 feb 13 12:50:48 +0900

Quoting Abhijit S. ([email protected]):

  1. The attached code gives me the following exception:
    /Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:84:
    warning: instance variable @orange_tree not initialized
    /Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:84:in
    <class:Worker>': undefined methodage’ for nil:NilClass
    (NoMethodError)
    from
    /Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:45:in
    `’

Instance variables are only accessible from within methods. Lines
83-86 are not within a method. Take lines 83-86 out of the class
definition, and change them to something like:

worker=Worker.new(Mutex.new,ConditionVariable.new,OrangeTree.new)
worker.do_some_work
until(worker.orange_tree.age==OrangeTree::AGE_TO_DIE)

wait for the Threads to finish

end

Of course, variable orange_tree has to be visible from the outside, so
you have to add something like

attr_reader(:orange_tree)

at the beginning of the Worker class. This way you should not have
those exceptions anymore.

Carlo

Tony A. wrote in post #1096818:

You might take a look at Celluloid:

Thanks but no. As I mentioned, I am new to Ruby and am practising. A
multithreading framework would not do much good in my learning process.

Carlo E. Prelz wrote in post #1096828:

Instance variables are only accessible from within methods. Lines
83-86 are not within a method. Take lines 83-86 out of the class
definition, and change them to something like:

worker=Worker.new(Mutex.new,ConditionVariable.new,OrangeTree.new)
worker.do_some_work
until(worker.orange_tree.age==OrangeTree::AGE_TO_DIE)

wait for the Threads to finish

end

Of course, variable orange_tree has to be visible from the outside, so
you have to add something like

attr_reader(:orange_tree)

at the beginning of the Worker class. This way you should not have
those exceptions anymore.

After making the changes you suggested and adding joins inside class
Worker, (file attached) I am now getting:

Orange picker going to sleep for 5
Orange picker woke up after sleeping for 5
Sorry, no Oranges to pick
Orange picker waiting patiently…
Age increaser going to sleep for 2
Age increaser woke up after sleeping for 2
/Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:76:in
join': deadlock detected (fatal) from /Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:76:indo_some_work’
from
/Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:82:in
<class:Worker>' from /Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:39:in

Ryan D. wrote in post #1096951:

That’s great. That’s the real problem the author wants you to solve. Sit
down with some paper and crayons / colored pencils and draw a picture of
the objects and what they have access to via your code. Then act out the
process. Can you understand how the deadlock occurs? What can you do to
avoid it?

If I were able to figure out the issue myself, why would I be sitting
here having this conversation? Do you see the problem? If yes, would you
be kind enough to point it out to me? If you do not see the problem
yourself, well, what can I say.

That’s not to say that I am going to sit with my thumb in my mouth while
someone else solves the problem for me. I am trying but a little help
goes a long way.

You might want to Google dining philosophers for another example.

I am very much aware of the Dining Philosophers example. It is not a
producer-consumer problem. It suggests a possible way to avoid deadlocks
which is to lock two shared resources in the same order in two competing
threads. I do not see the relevance with my issue here as I am not
dealing with two shared resources.

That’s great. That’s the real problem the author wants you to solve. Sit
down with some paper and crayons / colored pencils and draw a picture of
the objects and what they have access to via your code. Then act out the
process. Can you understand how the deadlock occurs? What can you do to
avoid it?

You might want to google dining philosophers for another example.

On Feb 14, 2013, at 13:10 , Abhijit S. [email protected] wrote:

be kind enough to point it out to me? If you do not see the problem
yourself, well, what can I say.

If I knew you were going to act like that I wouldn’t have bothered
writing in the first place.

Sit down away from your code. Draw out the problem on paper. Act it
out.

I actually like to act it out with one person per object, but sometimes
that is not feasible.

If you can’t figure it out from there, you’re probably not drawing the
problem out correctly or completely.

Abhijit S. wrote in post #1097009:

I rewrote the code using MonitorMixin and then back to using Mutex, same
result. I used 2 ConditionVariable, same result :frowning: If it’s not working,
it’s certainly not for the lack of trying.

How much have you tried? Your description not showing that… You should
Google first… Then try those all. Let us know what have you tried and
what you got.

thanks

Love U Ruby wrote in post #1097084:

How much have you tried? Your description not showing that… You should
Google first… Then try those all. Let us know what have you tried and
what you got.

Look who’s talking :slight_smile: the 1d10t trying to get even because he got
slapped on the wrist. Funny.

I rewrote the code using MonitorMixin and then back to using Mutex, same
result. I used 2 ConditionVariable, same result :frowning: If it’s not working,
it’s certainly not for the lack of trying.

Love U Ruby wrote in post #1097126:

No one here who can slap me … before that he will get it.
No one said anything about slapping you, though you have it coming for
sure. It’s an English phrase which, as usual, you did not try to
understand by Googling. Why am I not surprised?
I am curious though, how will you hurt anyone in the forum? Are you
going to piss yourself in anger and expect them to have a heart attack
in content?

Abhijit S. wrote in post #1097123:

Love U Ruby wrote in post #1097084:

How much have you tried? Your description not showing that… You should
Google first… Then try those all. Let us know what have you tried and
what you got.

Look who’s talking :slight_smile: the 1d10t trying to get even because he got
slapped on the wrist. Funny.

Don’t make me rude on you. don’t think yourself master on that. because
you didn’t invent Ruby.You are learning and me too. behave like an
educated guy.

No one here who can slap me … before that he will get it.

D. Deryl D. wrote:

Can you show the MonitorMixin complete error?

Entering Orange picker sync block…Entering age increaser sync block…

Entered age increaser sync block
Entered Orange picker sync block
C:/workspace/eclipse/ruby/learn_to_program/orange_tree.rb:108:in join': deadlock detected (fatal) from C:/workspace/eclipse/ruby/learn_to_program/orange_tree.rb:108:indo_some_work’
from C:/workspace/eclipse/ruby/learn_to_program/orange_tree.rb:116:in
block in <class:Worker>' from C:/workspace/eclipse/ruby/learn_to_program/orange_tree.rb:115:intimes’
from C:/workspace/eclipse/ruby/learn_to_program/orange_tree.rb:115:in
<class:Worker>' from C:/workspace/eclipse/ruby/learn_to_program/orange_tree.rb:45:in

Bump!
A problem unsolved keeps bugging me, I seriously hope the great minds
here would have something for me.

Abhijit S. wrote in post #1097129:

Love U Ruby wrote in post #1097126:

No one here who can slap me … before that he will get it.
No one said anything about slapping you, though you have it coming for
sure. It’s an English phrase which, as usual, you did not try to
understand by Googling. Why am I not surprised?
I am curious though, how will you hurt anyone in the forum? Are you
going to piss yourself in anger and expect them to have a heart attack
in content?

Don’t try to escape and you made that comments first. This is English
language forum. And I am least interested to do that. I don’t want to be
off topic.

go your own way…

On Sun, Feb 24, 2013 at 2:30 AM, Abhijit S. [email protected]
wrote:

Bump!
A problem unsolved keeps bugging me, I seriously hope the great minds
here would have something for me.

Just so we are sure we are all on the same level, can you please
repost the current version of the code? It may also help to get a
github account (free) and post all files in a single gist. That would
also allow for version tracking. It would also be help readability to
get rid of all the comments which contain alternative code.

I rechecked your posts and this seems to be the most recent version:
http://www.ruby-forum.com/attachment/8132/orange_tree.rb

I haven’t fully understood what you are trying to do here but a few
things occurred to me

  • OrangeTree apparently is the shared resource yet it does not include
    any synchronization logic.
  • you create the Worker inside Worker class context. While that may
    actually work it’s simpler to read to have your main program logic on
    the top level.
  • You have picked bad names for condition variables which convey
    nothing about their intended semantics.
  • Insert this line at the top of the script and you’ll know what’s going
    on:

Thread.abort_on_exception = true

Could be that you hit this bug - although I’d be surprised. What
version are you using.

Kind regards

robert

On Sun, Feb 24, 2013 at 2:12 PM, Sean O’Halpin [email protected]
wrote:

BTW Tony’s advice to look at DCell is good advice!

s/DCell/Celluloid/

On Sun, Feb 24, 2013 at 1:30 AM, Abhijit S. [email protected]
wrote:

Bump!
A problem unsolved keeps bugging me, I seriously hope the great minds
here would have something for me.

Hi,

I’ve created a gist at Attempt to get http://www.ruby-forum.com/attachment/8128/orange_tree.rb working - see http://www.ruby-forum.com/topic/4410866 · GitHub
which attempts to make your program do what I think it’s trying to do.
I’ve tried to keep as close to your original as possible so you can
see the differences more easily. There are other things I would change
but they are not relevant to this discussion. This is running on ruby
1.9.3p194.

A couple of points:

  • it’s really not clear what you’re trying to do - a problem
    definition would help.
  • as Robert pointed out, using Thread.abort_on_exception = true would
    help. You would have found that Math does not have a rand method for
    example.
  • the only shared state you need to protect access to is the
    @orange_count variable - it’s unnecessary to use synchronize around
    the whole loop. Also, that logic would probably be better encapsulated
    in the OrangeTree class itself.
  • you don’t check whether the exit condition for the orange_picker
    thread is true before waiting on the CV. This will halt your thread
    indefinitely when the age_increaser thread exits before the
    orange_picker thread starts the wait. Multiple checking of conditions
    is an unfortunate but necessary aspect of multi-threaded programming
    with mutexes, etc.
  • lastly, before you go down the rabbit hole of mutexes and condition
    variables, I would strongly advise you adopt the Actor model to
    isolate access to state and use queues to communicate between threads.
    You will save yourself a world of pain. BTW Tony’s advice to look at
    DCell is good advice!

Regards,
Sean

First of all, Sean’s modified version works!. It does not run into the
deadlock that mine does.
That being said, I’ll answer his questions below and, for newbies who
are facing similar problem like mine, also point out the difference that
made his code work and mine not.

Sean O’halpin wrote in post #1098770:

  • it’s really not clear what you’re trying to do - a problem
    definition would help.
    When I wrote this program about 3 weeks back, I was just starting to
    learn Ruby and I picked on different aspects of the language that I
    thought I needed practice on.
  • as Robert pointed out, using Thread.abort_on_exception = true would
    help. You would have found that Math does not have a rand method for
    example.
    Lesson learnt, didn’t know. I guess this is one issue with interpreted
    languages that you don’t see most problems until runtime.
  • the only shared state you need to protect access to is the
    @orange_count variable - it’s unnecessary to use synchronize around
    the whole loop.
    True. I thought then that @age also needs protection but on closer
    review now, it doesn’t.
  • you don’t check whether the exit condition for the orange_picker
    thread is true before waiting on the CV. This will halt your thread
    indefinitely when the age_increaser thread exits before the
    orange_picker thread starts the wait.
    THIS was causing the deadlock. When I added just the check to my
    program, it worked flawlessly. The missing check is the consequence of
    (wrongly) thinking that @age is also protected by the sync block.

Thank you much for taking the time to troubleshoot the issue. I’d also
attempt a Monitor version of the same program later but for now I am
working on the Ruby Q. challenges.