Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > Ruby 1.9, threads and FreeBSD 5

Reply
Thread Tools

Ruby 1.9, threads and FreeBSD 5

 
 
Eric Jacoboni
Guest
Posts: n/a
 
      02-20-2008
Hi,

Considering the following theory code:

require "thread"

ping = ConditionVariable.new
pong = ConditionVariable.new
mutex = Mutex.new

1.upto(10) do # 10 threads pong
Thread.new do
mutex.synchronize do
ping.wait(mutex)
puts("Pong...")
pong.signal
end
end
end

1.upto(10) do # 10 threads ping
Thread.new do
mutex.synchronize do
pong.wait(mutex)
puts("Ping...")
ping.signal
end
end
end

pong.signal # Go!

Thread.list.each { |t| t.join if t != Thread.main }


This code works as expected with Ruby 1.8 on FreeBSD and OS X :

% /usr/bin/ruby ping_pong_cond.rb
Ping...Pong...
Ping...Pong...
Ping...Pong...
Ping...Pong...
Ping...Pong...
Ping...Pong...
Ping...Pong...
Ping...Pong...
Ping...Pong...
Ping...Pong...


But it blocks with Ruby 1.9 on both OS :

% ruby ping_pong_cond.rb
^Cping_pong_cond.rb:30:in `join': Interrupt
from ping_pong_cond.rb:30:in `block in <main>'
from ping_pong_cond.rb:30:in `each'
from ping_pong_cond.rb:30:in `<main>'

Furthermore, it works fine with Ruby 1.9 on Vista.

As i know there is some change in Ruby threads/Native threads between
1.8 and 1.9, i suspect this change could be the culprit...

Any clue?

Thanks
 
Reply With Quote
 
 
 
 
MenTaLguY
Guest
Posts: n/a
 
      02-20-2008
On Wed, 20 Feb 2008 22:09:59 +0900, Eric Jacoboni <> wrote:
> 1.upto(10) do # 10 threads ping
> Thread.new do
> mutex.synchronize do
> pong.wait(mutex)
> puts("Ping...")
> ping.signal
> end
> end
> end
>
> pong.signal # Go!


Your code is buggy: there is a race condition such that the initial #signal
can get called before any corresponding #waits. That it superficially appeared
to work consistently with 1.8 was an accident of thread scheduling. Adding
a 'sleep' beforehand, as another poster suggested, makes it more likely to
work but does not actually fix the bug.

The correct approach is to use a synchronization primitive which queues
notifications, for example a semaphore or a queue, rather than a condition
variable. A queued notification can't get "missed" like this.

-mental


 
Reply With Quote
 
 
 
 
MenTaLguY
Guest
Posts: n/a
 
      02-20-2008
On Thu, 21 Feb 2008 03:33:23 +0900, MenTaLguY <> wrote:
> The correct approach is to use a synchronization primitive which queues
> notifications, for example a semaphore or a queue, rather than a condition
> variable. A queued notification can't get "missed" like this.


Specifically, if "Foo::Semaphore" were a counted semaphore class:

ping = Foo::Semaphore.new(0)
pong = Foo::Semaphore.new(0)

def event(wait, notify, message)
wait.down
puts message
notify.up
end

threads = (1..10).map {
[ Thread.new { event(pong, ping, "Ping...") },
Thread.new { event(ping, pong, "Pong...") } ]
}.flatten

pong.up

threads.each { |t| t.join }

Unfortunately there aren't any good 1.9/JRuby-friendly semaphore
implementations yet. Here is a very simple portable one (public domain):

require 'thread'

class PortableSemaphore
def initialize(count=0)
@lock = Mutex.new
@nonzero = ConditionVariable.new
@count = count
end

def down
@lock.synchronize do
@nonzero.wait @lock until @count.nonzero?
@count -= 1
end
self
end

def up
@lock.synchronize do
@count += 1
@nonzero.signal
end
self
end
end

Note that this is how condition variables are intended to be used --
not directly, but as building blocks for more useful primitives.

-mental


 
Reply With Quote
 
Eric Jacoboni
Guest
Posts: n/a
 
      02-20-2008
MenTaLguY <> writes:

> Your code is buggy: there is a race condition such that the initial #signal
> can get called before any corresponding #waits.


Gosh... you're right.

Thanks to Guy for the sleep trick and for your PortableSemaphore
implementation: i'm gonna investigate it further.
 
Reply With Quote
 
MenTaLguY
Guest
Posts: n/a
 
      02-20-2008
On Thu, 21 Feb 2008 05:59:57 +0900, Eric Jacoboni <> wrote:
> Thanks to Guy for the sleep trick and for your PortableSemaphore
> implementation: i'm gonna investigate it further.


Please note that you shouldn't ever use the sleep trick in
production code -- it merely hides problems during testing
when they can still occur under production load.

(I emphasize this, because the sleep trick seems to be fairly
popular as an "easy fix"; even I'm guilty of using it a lot
in the past...)

-mental



 
Reply With Quote
 
Eric Jacoboni
Guest
Posts: n/a
 
      02-20-2008
Mental Guy wrote:

> Please note that you shouldn't ever use the sleep trick in
> production code -- it merely hides problems during testing
> when they can still occur under production load.


Oh yes, i know synchronization should never relies on temporisations...
In this case, it's useful to point the bug you mention in my use of
#signal.

I've had wrote a general semaphore implementation using IO.pipe but your
PortableSemaphore is way more elegant... thanks a lot (i just wonder if
#up/#down honor the FIFO policy)

BTW, as for Queues : i admit i never use them.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
MenTaLguY
Guest
Posts: n/a
 
      02-20-2008
On Thu, 21 Feb 2008 06:51:18 +0900, Eric Jacoboni <> wrote:
> I've had wrote a general semaphore implementation using IO.pipe


That can still be useful sometimes -- for example, I wrote a
concurrent-selectable gem which provides latch, semaphore, and
channel (queue) implementations which can be passed as arguments to
IO.select, libev, etc. because they use IO.pipe underneath.

> PortableSemaphore is way more elegant... thanks a lot (i just wonder
> if #up/#down honor the FIFO policy)


It depends upon the implementation of ConditionVariable#wait. Some
Ruby implementations will wake threads in the order the threads called
#wait, and some will not. Most are roughly FIFO but not 100% "fair".

Fairness actually involves a tradeoff: while unfair blocking
primitives can sometimes lead to starvation (as sufficiently
greedy threads could keep "jumping the queue"), fair primitives
are more likely to have problems with convoying[1].

-mental

[1] Google "lock convoying"


 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Ruby 1.9, threads and FreeBSD 5 Eric Jacoboni Ruby 3 02-20-2008 05:39 PM
ruby gives different answer for checksum of files on windows and FreeBSD? Ralph Smith Ruby 6 10-26-2005 07:04 AM
Any movement on FreeBSD segfaults when using threads? Mike C. Fletcher Python 0 04-18-2005 04:55 PM
Problems with python and threads under Freebsd snacktime Python 4 02-08-2005 11:10 AM
Bug report: ruby-1.8.0p3 fails to compile under FreeBSD-4.7 Brian Candler Ruby 5 07-24-2003 06:26 AM



Advertisments
 



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57