Ruby 1.9, threads and FreeBSD 5

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

···

On Wed, 20 Feb 2008 22:09:59 +0900, Eric Jacoboni <jaco@neottia.net> 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!

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

···

On Thu, 21 Feb 2008 03:33:23 +0900, MenTaLguY <mental@rydia.net> 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.

MenTaLguY <mental@rydia.net> 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.

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

···

On Thu, 21 Feb 2008 05:59:57 +0900, Eric Jacoboni <jaco@neottia.net> wrote:

Thanks to Guy for the sleep trick and for your PortableSemaphore
implementation: i'm gonna investigate it further.

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

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"

···

On Thu, 21 Feb 2008 06:51:18 +0900, Eric Jacoboni <eric.jacoboni@gmail.com> wrote: