class SyncObject
def initialize
@semaphore = Mutex.new
end
def synchronize &block
@semaphore.synchronize &block
end
end
Before changing something in the sim objects, I synchronize them :
object.synchronize do
# change the state of the object
end
But as things were growing, I suddently got some deadlocks. After some research I found out, that a thread was invoking synchronize on the same object multiple times. The semaphore object doesn't care which thread is locking it. As long it's locked it will block all threads until the block is finished. Even if it's the same thread, that locked the semaphore just before.
But I needed a different behaviour, I want to synchronize the objects from different threads. As far as I remember Java's synchronize behaves the same way.
Using rspec we could specify this behaviour with 2 tests:
it "should not block the same thread when synchronizing" do
object = SyncObject.new
object.synchronize do
object.synchronize do
object.synchronize do
true.should be_true
end
end
end
end
it "should block two different threads when synchronizing" do
object = SyncObject.new
wait_for_me = true
object.synchronize do
Thread.fork do
object.synchronize do
wait_for_me = false
end
end
sleep 1
wait_for_me.should be_true
end
wait_for_me.should be_false
end
The first test's pretty straight forward, we want to allow nested synchronize blocks for the same thread.For the second test we need two different threads. The first thread locks the object and lunches a second thread. The first one returns immediately after fork, while the second one is launched in the meantime. So while the first is waiting for a second, the second thread is trying to achieve the lock and is blocked until the first one releases it on line 12. Only now the second thread gets access to the object and can set wait_for_me to false.
Of course both tests fail (or get some deadlocks) with the implemention above. The second version of synchronize looks now like this:
class SyncObject
def initialize
@semaphore = Mutex.new
end
def synchronize
unless @current_thread == Thread.current
@semaphore.synchronize do
@current_thread = Thread.current
yield
@current_thread = nil
end
else
yield
end
end
end
The first time synchronize is accessed, @current_thread is set to the current running thread. The semaphore is locked and the blocked yield. If in the meantime a different thread invokes synchronize, it will be blocked until the first one finishes the block. Else if the same thread access the method in the meantime, it will not try to lock the semaphore again, but can yield its new block immediately.
Keine Kommentare:
Kommentar veröffentlichen