Mutex confusion

I'm a little confused by Mutex's behaviour. It doesn't seem right that a
single thread should be able to block itself:

alex@twentyone:/tmp$ cat thread-block.rb
require 'thread'

m = Mutex.new

m.synchronize do
  m.synchronize do
    puts "Woo!"
  end
end

alex@twentyone:/tmp$ ruby thread-block.rb
thread-block.rb:6:in `synchronize': stopping only thread (ThreadError)
  note: use sleep to stop forever
  from thread-block.rb:6
  from thread-block.rb:5:in `synchronize'
  from thread-block.rb:5

Why does it behave like this? Is it traditional for mutexes (mutices?)
to be designed this way?

···

--
Alex
--
Posted via http://www.ruby-forum.com/.

Alex Young wrote:

I'm a little confused by Mutex's behaviour. It doesn't seem right that a
single thread should be able to block itself:

alex@twentyone:/tmp$ cat thread-block.rb
require 'thread'

m = Mutex.new

m.synchronize do
  m.synchronize do
    puts "Woo!"
  end
end

alex@twentyone:/tmp$ ruby thread-block.rb
thread-block.rb:6:in `synchronize': stopping only thread (ThreadError)
  note: use sleep to stop forever
  from thread-block.rb:6
  from thread-block.rb:5:in `synchronize'
  from thread-block.rb:5

Why does it behave like this? Is it traditional for mutexes (mutices?)
to be designed this way?

--
Alex
  
As far as "traditional" behavior, it can go either way. Sometimes counting semaphores are used, which may be acquired multiple times and must be released a corresponding number of times. POSIX threads, for example, provide both options. In this case, however, it is just a binary semaphore: either it is locked or not. The Mutex#synchronize call checks if the lock is available. If not (even it is the current thread that locked it), it blocks, as you noticed. That is its defined behavior in Ruby.

-Justin

Justin Collins wrote:

Alex Young wrote:

    puts "Woo!"
Why does it behave like this? Is it traditional for mutexes (mutices?)
to be designed this way?

--
Alex
  
As far as "traditional" behavior, it can go either way. Sometimes
counting semaphores are used, which may be acquired multiple times and
must be released a corresponding number of times. POSIX threads, for
example, provide both options. In this case, however, it is just a
binary semaphore: either it is locked or not. The Mutex#synchronize call
checks if the lock is available. If not (even it is the current thread
that locked it), it blocks, as you noticed. That is its defined behavior
in Ruby.

What would be broken if, hypothetically, the mutex behaviour were
changed not to block if the current thread already holds the lock? Would
any possible implementation introduce a race condition?

For a bit of background, this came up while writing a (fairly complex)
Capistrano recipe. Capistrano uses #set and #fetch methods to allow the
user to give lazily evaluated settings, like this:

    set(:foo){File.join(fetch(:deploy_to),"foo")}
    set(:deploy_to){"/tmp"}
    puts fetch(:foo) # <-- this is where the previous two blocks get
called

#set and #fetch both protect the inner variable in the same mutex (per
setting key, that is). This bit me when I was trying to set up a lazily
evaluated hash, like this:

    def add_to_settings(key,&val)
      set(:hsh, {}) unless exists?(:hsh)
      set(:hsh) do
        fetch(:hsh).merge(key => val.call)
      end
    end

The idea is that I'd build up a stack of procs which would be called
when, some time later, I called fetch(:hsh), to return a fully merged
hash. Unfortunately, because the inner fetch tries to synchronize on the
same mutex that the outer set already holds, this deadlocks. I've had to
sidestep set and fetch and forego any thread safety because of this, and
so far it hasn't been a problem.

Given that I've got a workaround, it's more interesting than annoying,
but I'm intrigued by the design decision.

···

--
Alex
--
Posted via http://www.ruby-forum.com/\.

Justin Collins wrote:

Alex Young wrote:

    puts "Woo!"
Why does it behave like this? Is it traditional for mutexes (mutices?)
to be designed this way?

--
Alex
  

As far as "traditional" behavior, it can go either way. Sometimes
counting semaphores are used, which may be acquired multiple times and
must be released a corresponding number of times. POSIX threads, for
example, provide both options. In this case, however, it is just a
binary semaphore: either it is locked or not. The Mutex#synchronize call
checks if the lock is available. If not (even it is the current thread
that locked it), it blocks, as you noticed. That is its defined behavior
in Ruby.

What would be broken if, hypothetically, the mutex behaviour were changed not to block if the current thread already holds the lock? Would any possible implementation introduce a race condition?

You want Monitor or MonitorMixin then. Mutex is just not implemented reentrant (for efficiency reasons I believe), that's all.

$ ruby19 -r monitor -e '[Monitor,Mutex].each {|m|x=m.new;x.synchronize {x.synchronize {puts m}}}'
Monitor
<internal:prelude>:6:in `lock': deadlock; recursive locking (ThreadError)
         from <internal:prelude>:6:in `synchronize'
         from -e:1:in `block (2 levels) in <main>'
         from <internal:prelude>:8:in `synchronize'
         from -e:1:in `block in <main>'
         from -e:1:in `each'
         from -e:1:in `<main>'

robert@fussel ~
$

Given that I've got a workaround, it's more interesting than annoying, but I'm intrigued by the design decision.

See above.

Kind regards

  robert

···

On 21.10.2009 08:02, Alex Young wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Alex Young wrote:

What would be broken if, hypothetically, the mutex behaviour were changed not to block if the current thread already holds the lock? Would any possible implementation introduce a race condition?

I'm just learning multi-threading with Ruby, don't like what I see, anyways Ruby doesn't support re-entrance with Mutex, it should but doesn't. There would be no adverse effect regrading re-entrance. A thread that owns the mutex should never be blocked, because its thread safe. Other threads would behave just like they do now, they would block on the mutex until it was released by the owning thread. There would be no racing condition, since the code is thread safe.

···

--
Alex

--
Kind Regards,
Rajinder Yadav

http://DevMentor.org
Do Good ~ Share Freely

Robert Klemme wrote:

What would be broken if, hypothetically, the mutex behaviour were changed not to block if the current thread already holds the lock? Would any possible implementation introduce a race condition?

You want Monitor or MonitorMixin then. Mutex is just not implemented reentrant (for efficiency reasons I believe), that's all.

I need to find a better source for Ruby multi-threading, did not know about monitors. Great I picked up something new today!

···

On 21.10.2009 08:02, Alex Young wrote:

Kind regards

    robert

--
Kind Regards,
Rajinder Yadav

http://DevMentor.org
Do Good ~ Share Freely