Concurrent Exchanger class in Ruby

I've always thought that java.util.concurrent.Exchanger was a nifty little class. The javadoc I've linked to describes it like this:

A synchronization point at which two threads can exchange objects. Each thread presents some object on entry to the exchange method, and receives the object presented by the other thread on return.

The javadoc also includes a nifty little example demonstrating why you might want an Exchanger object.

I decided to try to implement this in Ruby, to test my understanding of Mutex and ConditionVariable. The code is below. I'm not sure how useful it actually is, but it was fun to write. And if you can understand what it does, then you've got a good working knowledge of threads in Ruby!

Finally, if you've got the JDK installed, unzip the src.zip file and take a look at the source code for in java/util/concurrent/Exchanger.java. It is much more complicated than this trivial example. It was eye-opening to see how carefully written classes like this one are for high performance on multi-core or multi-CPU systems.

require 'thread'

class Exchanger
  def initialize
    # These variables will hold the two values to be exchanged
    @first_value = @second_value = nil
    # This Mutex protects access to the exchange method
    @lock = Mutex.new
    # This Mutex allows us to determine whether we're the first or
    # second thread to call exchange
    @first = Mutex.new
    # This ConditionVariable allows the first thread to wait for
    # the arrival of the second thread
    @second = ConditionVariable.new
  end

  # Exchange this value for the value passed by the other thread
  def exchange(value)
    @lock.synchronize do      # Only one thread can call this method at a time
      if (@first.try_lock)    # We are the first thread
        @first_value = value  # Store the first thread's argument
        # Now wait until the second thread arrives.
        # This temporarily unlocks the Mutex while we wait, so 
        # that the second thread can call this method, too
        @second.wait(@lock)   # Wait for second thread 
        @first.unlock         # Get ready for the next exchange
        @second_value         # Return the second thread's value
      else                    # Otherwise, we're the second thread
        @second_value = value # Store the second value
        @second.signal()      # Tell the first thread we're here
        @first_value          # Return the first thread's value
      end
    end
  end
end

# This is some test code

e = Exchanger.new

5.times do
  t1 = Thread.new {
    sleep(rand)
    puts "t1 exchanges 1 for #{e.exchange(1)}"
  }
  t2 = Thread.new {
    sleep(rand)
    puts "t2 exchanges 2 for #{e.exchange(2)}"
  }

  t1.join; t2.join
end

Books

Comprehensive coverage of Ruby 1.8 and 1.9

"The New Most Important Ruby Book"
Peter Cooper,
rubyinside.com

Completely updated for Ajax and Web 2.0

"A must-have reference"
Brendan Eich,
creator of JavaScript

The classic Java quick-reference

Advertising

Pages

Hosted By

Powered by Movable Type 4.21-en