| [ Team LiB ] |
|
Mutexes and Condition VariablesMutexes and condition variables are thread synchronization mechanisms. Although they are used frequently in other languages, they aren't needed as often in Tcl because of Tcl's threading model and the atomic nature of all shared variable commands. All mutex and condition variable commands are provided by the Thread extension in the thread namespace. MutexesA mutex, which is short for mutual exclusion, is a locking mechanism. You use a mutex to protect shared resources — such as shared variables, serial ports, databases, etc. — from concurrent access by multiple threads. Before accessing the shared resource, the thread attempts to lock the mutex. If no other thread currently holds the mutex, the thread successfully locks the mutex and can access the resource. If another thread already holds the mutex, then the attempt to lock the mutex blocks until the other thread releases the mutex. This sequence is illustrated in Example 21-13. The first step is creating a mutex with the thread::mutex create operation, which returns a unique token representing the mutex. The same token is used in all threads, and so you must make this token available (for example, through a shared variable) to all threads that access the shared resource. Example 21-13 Using a mutex to protect a shared resource# Create the mutex, storing the mutex token in a shared # variable for other threads to access. tsv::set db mutex [thread::mutex create] # ... # Lock the mutex before accessing the shared resource. thread::mutex lock [tsv::get db mutex] # Use the shared resource, and then unlock the mutex. thread::mutex unlock [tsv::get db mutex] # Lather, rinse, repeat as needed... thread::mutex destroy [tsv::get db mutex]
Mutexes work only if all threads in an application use them properly. A "rogue" thread can ignore using a mutex and access the shared resource directly. Therefore, you should be very careful to use your mutexes consistently when designing and implementing your application. Condition VariablesA condition variable is a synchronization mechanism that allows one or more threads to sleep until they receive notification from another thread. A condition variable is associated with a mutex and a boolean condition known as a predicate. A thread uses the condition variable to wait until the boolean predicate is true. A different thread changes the state of the predicate to true, and then notifies the condition variable. The mutex synchronizes thread access to the data used to compute the predicate value. The general usage pattern for the signalling thread is:
The pattern for a waiting thread is:
In practice, a waiting thread should always check the predicate inside a while loop, because multiple threads might be waiting on the same condition variable. A waiting thread automatically releases the mutex when it waits on the condition variable. When the signalling thread notifies the condition variable, all threads waiting on that condition variable compete for a lock on the mutex. Then when the signalling thread releases the mutex, one of the waiting threads gets the lock. It is quite possible for that thread then to change the state so that the predicate is no longer true when it releases the lock. For example, several worker threads forming a thread pool might wait until there is some type of job to process. Upon notification, the first worker thread takes the job, leaving nothing for the other worker threads to process. This sequence for using a condition variable sounds complex, but is relatively easy to code. Example 21-14 shows the sequence for the signalling thread. The first step is creating a condition variable with the thread::cond create operation, which returns a unique token representing the condition variable. As with mutexes, the same token is used in all threads, and so you must make this token available (for example, through a shared variable) to all threads that access the condition variable. When the thread is ready to update the predicate, it first locks the associated mutex. Then it notifies the condition variable with thread::cond notify and finally unlocks the mutex. Example 21-14 Standard condition variable use for a signalling thread# Create the condition variable and accompanying mutex. # Use shared variables to share these tokens with all other # threads that need to access them. set cond [tsv::set tasks cond [thread::cond create]] set mutex [tsv::set tasks mutex [thread::mutex create]] # When we're ready to update the state of the predicate, we # must first obtain the mutex protecting it. thread::mutex lock $mutex # Now update the predicate. In this example, we'll just set a # shared variable to true. In practice, the predicate can be # more complex, such as the length of a list stored in a # shared variable being greater than 0. tsv::set tasks predicate 1 # Notify the condition variable, waking all waiting threads. # Each thread will block until it can lock the mutex. thread::cond notify $cond # Unlock the mutex. thread::mutex unlock $mutex Example 21-15 shows the sequence for a waiting thread. When a thread is ready to test the predicate, it must first lock the mutex protecting it. If the predicate is true, the thread can continue processing, unlocking the mutex when appropriate. If the predicate is false, the thread executes thread::cond wait to wait for notification. The thread::cond wait command atomically unlocks the mutex and puts the thread into a wait state. Upon notification, the thread atomically locks the mutex (blocking until it can obtain it) and returns from the thread::cond wait command. It then tests the predicate, and repeats the process until the predicate is true. Example 21-15 Standard condition variable use for a waiting thread
set mutex [tsv::get tasks mutex]
set cond [tsv::get tasks cond]
# Lock the mutex before testing the predicate.
thread::mutex lock $mutex
# Test the predicate, if necessary waiting until it is true.
while {![tsv::get tasks predicate]} {
# Wait for notification on the condition variable.
# thread::cond wait internally unlocks the mutex,
# blocks until it receives notification, then locks
# the mutex again before returning.
thread::cond wait $cond $mutex
}
# We now hold the mutex and know the predicate is true. Do
# whatever processing is desired, and unlock the mutex when
# it is no longer needed.
thread::mutex unlock $mutex
Tcl's threading model greatly reduces the need for condition variables. It's usually much simpler to place a thread in its event loop with thread::wait, and then send it messages with thread::send. And for applications where you want a thread pool to handle jobs on demand, the Thread extension's built-in thread pool implementation is far easier than creating your own with condition variables. |
| [ Team LiB ] |
|