Recipe 20.4. Synchronizing Access to an ObjectProblemYou want to make an object accessible from only one thread at a time. SolutionGive the object a This code gives every object a
Here's 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't get a chance until the first thread releases the lock.
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:
DiscussionOne 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't use it. When every object has a synchronize method, it's 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't 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:
Any AspectR aspect method needs to take three arguments: the symbol of the method being called, the object it's 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're going to lock and unlock. Let's use the Synchronized aspect to create an array where you can only call push, pop, or each once you get an exclusive lock.
The call to wrap tells AspectR to modify our array's 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't run until the first thread finishes its call to each, and the AspectR-generated code calls unlock.
See Also
|
Thursday, October 29, 2009
Recipe 20.4. Synchronizing Access to an Object
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment