This morning I was making some Ruby code of mine thread-safe which is always fun. (I'm serious btw. I frikking love multithreaded programming!) In doing so I came across something that I found a bit surprising.
Consider the following snippet: Think it will work? Let's try...
<internal:prelude>:8:in `lock': deadlock; recursive locking (ThreadError)
from :8:in `synchronize'
from reentrancy.rb:5:in `block in '
from :10:in `synchronize'
from reentrancy.rb:4:in `'
Shocking!
Mutex is not reentrant. Wow. Ok. Let's try something else...
Let's change that Mutux
into a Monitor
and try again.
Alrighty, let's put on fresh underwear and give it a whirl...
Monitor is reentrant.
Ah, the world makes sense again. If I had to code my own reentrancy I would've cried and hated Ruby a little bit. My love and faith in Ruby remains, yay!
Nothing is free. Is there a performance penalty? Time for some benchmarks.
Here is a little benchmarking script that acquires and releases both a mutex and monitor 1 million times each:
Benchmarking results:
user system total real
Mutex 0.400000 0.000000 0.400000 ( 0.406259)
Monitor 0.870000 0.010000 0.880000 ( 0.864888)
Ouch, monitor takes over the double the time that mutex does. That's the trade-off.
I'm curious, let's try JRuby too. We'll change bm
to bmbm
and fire it up.
Rehearsal ---------------------------------------------
Mutex 0.571000 0.000000 0.571000 ( 0.539000)
Monitor 2.012000 0.000000 2.012000 ( 2.012000)
------------------------------------ total: 2.583000sec
user system total real
Mutex 0.321000 0.000000 0.321000 ( 0.321000)
Monitor 1.696000 0.000000 1.696000 ( 1.696000)
Wow, Monitor is 5.3x slower when using JRuby!!! Hmmm, I suspect JIT just need more time to warmup. Here's a new benchmarking script with a big warmup:
And the results:
> jruby --1.9 --fast reentrancy-benchmark-jruby.rb
Warmup #1/20
...
Warmup #20/20
user system total real
Mutex 0.357000 0.000000 0.357000 ( 0.357000)
Monitor 0.768000 0.000000 0.768000 ( 0.768000)
Ok, that's on-par with the MRI results. Mutex is fast off-the-bat with JRuby where as Monitor will be a lot slower at first then decrease to a little over double the speed of mutex.
Mutex: No reentrancy. Fast, less than half the speed of Monitor.
Monitor: Reentrancy. Slow, little over twice as slow as Mutex.