Does ruby have the Java equivalent of synchronize keyword? I am using 1.9.1 and I don't quite see an elegant way to do this.
2 Answers
It doesn't have the synchronize
keyword, but you can get something very similar via the Monitor
class. Here's an example from the Programming Ruby 1.8 book:
require 'monitor'
class Counter < Monitor
attr_reader :count
def initialize
@count = 0
super
end
def tick
synchronize do
@count += 1
end
end
end
c = Counter.new
t1 = Thread.new { 100_000.times { c.tick } }
t2 = Thread.new { 100_000.times { c.tick } }
t1.join; t2.join
c.count → 200000

- 87,773
- 37
- 126
- 127
-
7This is BTW a general programming language design principle: only crappy programming languages need keywords or new language features for everything. Well-designed languages can do it simply in a library. – Jörg W Mittag Jul 08 '10 at 22:57
-
9You also don't have to inherit from Monitor if you don't want to. Just include the MonitorMixin module (which also gets brought in by `require 'monitor'`) in your class and you get the same behavior for free. – Chuck Jul 08 '10 at 23:09
-
4@JörgWMittag Looks like we found a LISP programmer ;) – jklp Mar 24 '15 at 06:37
-
I just comment out `synchronize do` and answer still the same: `200_000`. Where is the trick? – skywinder Jun 15 '15 at 07:01
-
I add another answer with explanation how `synchronize` works. http://stackoverflow.com/a/30840316/1698467 Hope it helps to feel the difference. – skywinder Jun 15 '15 at 08:27
The accepted answer doesn't represent how synchronize
works!
You can just comment out synchronize do
and run accepted answer's script - output will be the same: 200_000
!
So, here is an example, to show the difference between running with/without synchronize
block:
Not thread safe example:
#! /usr/bin/env ruby
require 'monitor'
class Counter < Monitor
attr_reader :count
def initialize
@count = 0
super
end
def tick i
puts "before (#{ i }): #{ @count }"
@count += 1
puts "after (#{ i }): #{ @count }"
end
end
c = Counter.new
3.times.map do |i|
Thread.new do
c.tick i
end
end.each(&:join)
puts c.count
In the output you will get sometihing like that:
before (1): 0
after (1): 1
before (2): 0
before (0): 0 <- !!
after (2): 2
after (0): 3 <- !!
Total: 3
When the thread (0)
started, count
was equal to 0
, but after adding +1
its value was 3
.
What happens here?
When the threads are starting they see the initial value of count
. But when each of them, try to add +1
, the value became different as result of the parallel computation. Without a proper synchronization, the partial state of count
is unpredictable.
Atomicity
Now we call these operations atomic:
#! /usr/bin/env ruby
require 'monitor'
class Counter < Monitor
attr_reader :count
def initialize
@count = 0
super
end
def tick i
synchronize do
puts "before (#{ i }): #{ @count }"
@count += 1
puts "after (#{ i }): #{ @count }"
end
end
end
c = Counter.new
3.times.map do |i|
Thread.new do
c.tick i
end
end.each(&:join)
puts c.count
Output:
before (1): 0
after (1): 1
before (0): 1
after (0): 2
before (2): 2
after (2): 3
Total: 3
Now, by using synchronize
block, we ensure the atomicity of the add operation.
but threads still running in random order (1->0->2)
For detailed explanation, your can continue reading this article.