Mutex confusion

A

Alex Young

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?
 
J

Justin Collins

Alex said:
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?

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
 
A

Alex Young

Justin said:
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.
 
R

Robert Klemme

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
 
R

Rajinder Yadav

Alex said:
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.


--
Kind Regards,
Rajinder Yadav

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

Rajinder Yadav

Robert said:
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!
Kind regards

robert


--
Kind Regards,
Rajinder Yadav

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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,985
Messages
2,570,199
Members
46,766
Latest member
rignpype

Latest Threads

Top