| |||||||
Ruby's built-in threads are both useful and easy to use. However, if your threads need to communicate, or they need to share data or other resources, some form of synchronization will be needed. The lowest-level method to block other threads from running is , which sets a global ``thread critical'' condition. When set
to true, the scheduler will not schedule any existing thread to run.
However, this does not block new threads from being created and run.
Certain thread operations (such as stopping or killing a thread, sleeping
in the current thread, or raising an exception) may cause a thread to be
scheduled even when in a critical section.
Using #critical= directly is certainly possible, but
it isn't terribly convenient. Fortunately, Ruby comes packaged with
several alternatives. Of these, two of the best, class Mutex
and class ConditionalVariable are available in the
thread library module.
The Mutex ClassMutex is a class which implements a
simple semaphore lock for mutually exclusive access to some shared
resource. That is, only one thread may hold the lock at a given time.
Other threads may choose to wait in line for the lock to become available,
or may simply choose to get an immediate error indicating that the lock is
not available.
A mutex is often used when updates to shared data need to be
atomic. Say we need to update two variables as part of a transaction. We
can simulate this in a trivial program by incrementing some counters. The
updates are supposed to be atomic---the outside world should never see the
counters with different values. Without any kind of mutex control, this
just doesn't work. This example shows that the ``spy'' thread woke up a large number of
times and found the values of count1 and count2
inconsistent.
Fortunately we can fix this using a mutex. By placing all accesses to the shared data under control of a mutex,
we ensure consistency. Unfortunately, as you can see from the numbers, we
also experience quite a performance penalty.
Condition VariablesUsing 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 as they cannot enter the critical section---the original process still has it locked. You need to be able to temporarily give up your exclusive use of the critical region and simultaneously tell people that you're waiting for a resource. When the resource comes available, you need to be able to grab it and re-obtain the lock on the critical region, all in one step. This is where condition variables come in. A condition variable is simply a semaphore which is associated with a resource, and which 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. For alternative implementations of synchronization mechanisms, see
monitor.rb and sync.rb in the lib
subdirectory of the distribution.
Copyright (c) 2001, The Pragmatic Programmers
| |||||||
| |||||||
Copyright © 2001 The Pragmatic Programmers, LLC All Rights Reserved |