Using a mutex to protect critical data is sometimes not enough.
Suppose you are in a critical section, but you need to wait for some
particular resource. If your thread goes to sleep waiting for this
resource, it is possible that no other thread will be able to release
the resource because it cannot enter the critical section---the original
process still has it locked. You need to be able to give up temporarily
your exclusive use of the critical region and simultaneously tell
people that you're waiting for a resource. When the resource becomes
available, you need to be able to grab it
and reobtain the
lock on the critical region, all in one step.
This is where condition variables come in. A condition variable is
simply a semaphore that is associated with a resource and is
used within the protection of a particular mutex. When you need a
resource that's unavailable, you wait on
a condition variable. That action releases the lock on the
corresponding mutex. When some other thread signals that the resource
is available, the original thread comes off the wait and
simultaneously regains the lock on the critical region.
require 'thread'
mutex = Mutex.new
cv = ConditionVariable.new
a = Thread.new {
mutex.synchronize {
puts "A: I have critical section, but will wait for cv"
cv.wait(mutex)
puts "A: I have critical section again! I rule!"
}
}
puts "(Later, back at the ranch...)"
b = Thread.new {
mutex.synchronize {
puts "B: Now I am critical, but am done with cv"
cv.signal
puts "B: I am still critical, finishing up"
}
}
a.join
b.join
|
produces:
A: I have critical section, but will wait for cv(Later, back at the ranch...)
B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!
|
For alternative implementations of synchronization mechanisms, see
monitor.rb
and
sync.rb
in the
lib
subdirectory of the
distribution.