Does ruby have the Java equivalent of synchronize keyword?
Asked Answered
N

2

13

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.

Niki answered 8/7, 2010 at 22:26 Comment(1)
You may want to try my Ruby gem syncem, which will make all methods in your object synchronizedHeterogenous
C
16

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
Capacitate answered 8/7, 2010 at 22:32 Comment(5)
This 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.Denote
You 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.Housebroken
@JörgWMittag Looks like we found a LISP programmer ;)Accrue
I just comment out synchronize do and answer still the same: 200_000. Where is the trick?Bourn
I add another answer with explanation how synchronize works. https://mcmap.net/q/663933/-does-ruby-have-the-java-equivalent-of-synchronize-keyword Hope it helps to feel the difference.Bourn
B
14

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.

Bourn answered 15/6, 2015 at 8:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.