Synchronizing Access to an Object

Problem

You want to make an object accessible from only one thread at a time.

Solution

Give the object a Mutex member (a semaphore that controls whose turn it is to use the object). You can then use this to synchronize activity on the object.

This code gives every object a synchronize method. This simulates the behavior of Java, in which synchronize is a keyword that can be applied to any object:

	require 	hread
	class Object
	 def synchronize
	 mutex.synchronize { yield self }
	 end

	 def mutex
	 @mutex ||= Mutex.new
	 end
	end

Heres an example. The first thread gets a lock on the list and then dawdles for a while. The second thread is ready from the start to add to the list, but it doesn get a chance until the first thread releases the lock.

	list = []
	Thread.new { list.synchronize { |l| sleep(5); 3.times { l.push "Thread 1" } } }
	Thread.new { list.synchronize { |l| 3.times { l.push "Thread 2" } } }
	sleep(6)
	list
	# => ["Thread 1", "Thread 1", "Thread 1", "Thread 2", "Thread 2", "Thread 2"]

Object#synchronize only prevents two synchronized code blocks from running at the same time. Nothing prevents a wayward thread from modifying the object without calling synchronize first:

	list = []
	Thread.new { list.synchronize { |l| sleep(5); 3.times { l.push "Thread 1" } } }
	Thread.new { 3.times { list.push "Thread 2" } }
	sleep(6)
	list
	# => ["Thread 2", "Thread 2", "Thread 2", "Thread 1", "Thread 1", "Thread 1"]

Discussion

One of the big advantages of multithreaded programs is that different threads can share data. But where there is data sharing, there is the possibility for corruption. When two threads operate on the same object at the same time, the results can vary wildly depending on when the Ruby interpreter decides to switch between threads. To get predictable behavior, you need to have one thread lock the object, so other threads can use it.

When every object has a synchronize method, its easier to share an object between threads: if you want to work alone with the object, you put that code within a synchronize block. Of course, you may find yourself constantly writing synchronization code whenever you call certain methods of an object.

It would be nice if you could to do this synchronization implicitly, the way you can in Java: you just designate certain methods as "synchronized," and the interpreter won start running those methods until it can obtain an exclusive lock on the corresponding object. The simplest way to do this is to use aspect-oriented programming. The RAspect library described in Recipe 10.15 can be used for this.

The following code defines an Aspect that can wrap methods in synchronization code. It uses the Object#mutex method defined above, but it could easily be changed to define its own Mutex objects:

	require aspectr
	require 	hread

	class Synchronized < AspectR::Aspect
	 def lock(method_sym, object, return_value, *args)
	 object.mutex.lock
	 end

	 def unlock(method_sym, object, return_value, *args)
	 object.mutex.unlock
	 end
	end

Any AspectR aspect method needs to take three arguments: the symbol of the method being called, the object its being called on, and (if the aspect method is being called after the original method) the return value of the method.

The rest of the arguments are the arguments to the original method. Since this aspect is very simple, the only argument we need is object, the object we e going to lock and unlock.

Lets use the Synchronized aspect to create an array where you can only call push, pop, or each once you get an exclusive lock.

	array = %w{do re mi fa so la ti}
	Synchronized.new.wrap(array, :lock, :unlock, :push, :pop, :each)

The call to wrap tells AspectR to modify our arrays implementation of push, pop, and each with generated singleton methods. Synchronized#lock is called before the old implementation of those methods is run, and Synchronized#unlock is called afterward.

The following example creates two threads to work on our synchronized array. The first thread iterates over the array, and the second thread destroys its contents with repeated calls to pop. When the first thread calls each, the AspectR-generated code calls lock, and the first thread gets a lock on the array. The second thread starts and it wants to call pop, but pop has been modified to require an exclusive lock on the array. The second thread can run until the first thread finishes its call to each, and the AspectR-generated code calls unlock.

	Thread.new { array.each { |x| puts x } }
	Thread.new do
	 puts Destroying the array.
	 array.pop until array.empty?
	 puts Destroyed!
	end
	# do
	# re
	# mi
	# fa
	# so
	# la
	# ti
	# Destroying the array.
	# Destroyed!

See Also

  • See Recipe 10.15, "Doing Aspect-Oriented Programming," especially for information on problems with AspectR when wrapping operator methods in aspects
  • Recipe 13.17, "Adding Hooks to Table Events," demonstrates the aspect oriented programming features of the Glue library, which are simpler than AspectR (but actually, in my experience, more difficult to use)
  • Recipe 16.10, "Sharing a Hash Between Any Number of Computers," has an alternate solution: it defines a delegate class ( ThreadsafeHash) whose method_missing implementation synchronizes on a mutex and then delegates the method call; this is an easy way to synchronize all of an objects methods
  • Recipe 20.11, "Avoiding Deadlock"


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net