Add Book to My BookshelfPurchase This Book Online

Chapter 4 - Managing Pthreads

Pthreads Programming
Bradford Nichols, Dick Buttlar and Jacqueline Proulx Farrell
 Copyright © 1996 O'Reilly & Associates, Inc.

Mutex Scheduling Attributes
We may take great pains to apply scheduling to the threads in our program, designating those threads that should be given preferential access to the CPU when they're ready to run. However, what if our high priority threads must contend for the same resources as our lower priority threads? It's likely that at times a high priority thread will stall waiting for a mutex lock held by a lower priority thread. This is the priority inversion phenomenon of which we spoke earlier. The mutex plainly doesn't recognize that some threads that ask for it are more important than others.
Consider a real-time multithreaded application that controls the operation of a power plant. One controls fuel intake and must react quickly and predictably to changes in flow rate and line pressure; this thread has high priority. Another thread collects statistics on plant operations for monthly reports and collects information on the state of the plant once an hour; this thread is assigned a lower priority. An additional thread, of medium priority in the application, perhaps, faxes sandwich orders at lunch time.
Both the fuel-control and statistic-gathering threads must control a mechanical arm to position a temperature sensor at various locations within the plant to take temperature readings. Each contends for a single mutex that synchronizes access to the arm.
We'll start with a situation in which all threads are blocked and the mutex is unlocked. Suppose that the sequence of events listed in the left column of Table 4-2 occurs. The statistics-gathering thread runs first, grabs the mutex, and ends up by blocking the fuel-control thread that is ready to run.
Table 4-2: Priority Inversion in a Power Plant Application (1)
Event
Fuel Control Thread
Medium Priority Thread
Statistics Gathering Thread
Arm Mutex
Start
Blocked
Blocked
Blocked
Unlocked
The statistics-gathering thread must take a temperature.
Blocked
Blocked
Running
Unlocked
The statistics-gathering thread acquires the mutex.
Blocked
Blocked
Running
Locked by statistics-gathering thread
An event occurs, waking the fuel-control thread. It preempts the statistics thread.
Running
Blocked
Runnable
Locked by statistics-gathering thread
The fuel-control thread tries to get the mutex and blocks. The statistics thread regains the CPU.
Blocked on mutex
Blocked
Running
Locked by statistics-gathering thread
The situation can actually get worse when, as shown in Table 4-3, the medium priority thread awakens. It has a higher priority than the statistics-gathering thread and does not need to wait for the mutex the medium thread currently holds. It's runnable and will preempt the statistics-gathering thread. Now the fuel-control thread must wait for the medium priority thread, too—and this thread doesn't even need to use the arm!
Table 4-3: Priority Inversion in a Power Plant Application (2)
Event
Fuel- Control Thread
Medium Priority Thread
Statistics- Gathering Thread
Arm Mutex
An event occurs, waking the medium priority thread. It preempts the statistics-gathering thread.
Blocked on mutex
Running
Runnable
Locked by statistics-gathering thread
If the sirens and flashing lights weren't so distracting, we'd redesign the application so that the fuel-control and statistics-gathering threads no longer use a common resource. But we need to introduce a new Pthreads feature, and besides, we have only so much time before we have to evacuate the plant.
The Pthreads standard allows (but does not require) implementations to design mutexes that can give a priority boost to low priority threads that hold them. We can associate a mutex with either of two priority protocols that provide this feature: priority ceiling or priority inheritance. We'll start with a discussion of priority ceiling, the simpler of the two protocols.
Priority Ceiling
The priority ceiling protocol associates a scheduling priority with a mutex. Thus equipped, a mutex can assign its holder an effective priority equal to its own, if the mutex holder has a lower priority to begin with.
Let's apply this feature to our power plant example and see what happens. We'll associate a high priority with the mutex that controls access to the arm and revisit the earlier sequence of events. Table 4-4 illustrates the results.
Table 4-4: Priority Inversion in a Power Plant Application
Event
Fuel- Control Thread
Medium Priority Thread
Statistics- Gathering Thread
Arm Mutex (High Priority)
Start
Blocked
Blocked
Blocked
Unlocked
The statistics- gathering thread must take a temperature.
Blocked
Blocked
Running
Unlocked
The statistics- gathering thread acquires the mutex. It gets an effective priority of high.
Blocked
Blocked
Running
Locked by statistics-gathering thread
An event occurs, waking the fuel-control thread. It does not preempt the statistics- gathering thread, which is also at high priority.
Runnable
Blocked
Running
Locked by statistics-gathering thread
An event occurs, waking the medium priority thread. It does not preempt the statistics- gathering thread, which is also at high priority.
Runnable
Runnable
Running
Locked by statistics-gathering thread
At this point, the statistics-gathering thread will complete its operation at the highest priority and in the shortest period of time. Table 4-5 shows the sequence of events that occurs when it releases the mutex.
Table 4-5: Priority Inversion in a Power Plant Application
Event
Fuel- Control Thread
Medium Priority Thread
Statistics- Gathering Thread
Arm Mutex (High Priority)
The statistics- gathering thread unlocks the mutex. It reverts to low priority and is preempted by the highest priority runnable thread. This is the fuel-control thread
Running
Runnable
Runnable
Unlocked
The fuel-control thread tries to get the mutex and succeeds.
Running
Runnable
Running
Locked by fuel thread
Now the fuel-control thread can do its work, having to wait only for the statistics-gathering thread—not for the medium priority thread as well. Although the fuel-control thread must wait, it waits for a shorter period of time and in a more predictable manner.
If your platform supports the priority ceiling protocol, the compile-time constant _POSIX_THREAD_PRIO_PROTECT will be defined. Example 4-27 shows how to create a mutex that uses the priority ceiling protocol.
Example 4-27: Setting a Priority Ceiling on a Mutex (mutex_ceiling.c)
pthread_mutex_t m1;
pthread_mutexattr_t mutexattr_prioceiling;
int mutex_protocol, high_prio;
.
high_prio = sched_get_priority_max(SCHED_FIFO);
.
pthread_mutexattr_init(&mutexattr_prioceiling);
pthread_mutexattr_getprotocol(&mutexattr_prioceiling, &mutex_protocol);
pthread_mutexattr_setprotocol(&mutexattr_prioceiling, PTHREAD_PRIO_PROTECT);
pthread_mutexattr_setprioceiling(&mutexattr_prioceiling, high_prio);
pthread_mutex_init(&m1, &mutexattr_prioceiling);
We first declare a mutex attribute object (pthread_mutex_attr_t) and initialize it by calling pthread_mutexattr_init. Our call to pthread_mutexattr_getprotocol returns the priority protocol that is associated with our mutex by default. The priority protocol attribute can have one of three values:
 PTHREAD_PRIO_NONE
The mutex uses no priority protocol.
 PTHREAD_PRIO_PROTECT
The mutex uses the priority ceiling protocol.
 PTHREAD_PRIO_INHERIT
The mutex uses the priority inheritance protocol.
If the pthread_mutexattr_getprotocol call does not show that the mutex is using the priority ceiling protocol, we call the pthread_mutexattr_setprotocol function to set this protocol in the mutex's attribute object. After we've done so, we call pthread_mutexattr_setprioceiling to set the fixed priority ceiling attribute in the mutex object. (Conversely, a call to pthread_mutexattr_getprioceiling would return the current value of this attribute.) The priority passed is an integer argument set up in the same manner as a thread's priority value. Finally, we initialize the mutex by specifying the mutex attribute object to pthread_mutex_init.
Priority Inheritance
The priority inheritance protocol lets a mutex elevate the priority of its holder to that of the waiting thread with the highest priority. If we applied the priority inheritance protocol to the arm mutex in our power plant example, the result would be that the statistics-gathering thread wouldn't unconditionally receive a priority boost as soon as it won the mutex lock; it would be elevated to high priority only when the fuel-control thread starts to wait on the mutex. Because the priority inheritance protocol awards a priority boost to a mutex holder only when it's absolutely needed, it can be more efficient than the priority ceiling protocol.
If your platform supports the priority inheritance feature, the compile-time constant _POSIX_THREAD_PRIO_INHERIT will be TRUE. Example 4-28 shows how to create a mutex with the priority inheritance attribute. The process is nearly identical to the one we used to set up the priority ceiling protocol for the mutex in Example 4-27.
Example 4-28: Setting Priority Inheritance on a Mutex (mutex_priority.c)
pthread_mutex_t m1;
pthread_mutexattr_t mutexattr_prioinherit;
int mutex_procotol;
.
.
.
pthread_mutexattr_init(&mutexattr_prioinherit);
pthread_mutexattr_getprotocol(&mutexattr_prioinherit, &mutex_protocol);
if (mutex_protocol != PTHREAD_PRIO_INHERIT) {;
    pthread_mutexattr_setprotocol(&mutexattr_prioinherit, PTHREAD_PRIO_INHERIT);
}
pthread_mutex_init(&m1, &mutexattr_prioinherit);
The ATM Example and Priority Inversion
Let's return to our ATM server example. In its most recent version, we introduced a scheduling framework and started to assign different priorities to different threads. Having done so, we've introduced a risk that our threads may encounter priority inversion situations. A high priority thread could attempt to perform a deposit transaction on the same account for which a low priority thread is already processing a different transaction. When it does so, the high priority thread will very likely need to wait on the mutex that the low priority thread currently holds. We can help out our high priority threads by assigning this mutex a scheduling attribute of some sort.
Which protocol should we use—priority ceiling or priority inheritance? If we use the priority ceiling protocol, we would have to associate a very high priority ceiling with the mutexes that guard the accounts. Overall, this would have a rather negative effect on our server's behavior and the performance of deposit transactions in particular. Low priority threads would always be given a priority boost whenever they obtained a mutex, regardless of whether a deposit thread needs to lock the same mutex. Because each worker thread holds the mutex for an account for a significant length of time, the scheduler's priority queues would fill with runnable, high priority threads. A deposit thread would just be another high priority thread in the queue and would not get any special treatment. This is not what we want.
For a program like our ATM server, it makes much more sense to use the priority inheritance protocol. If we assign the priority inheritance attribute to each of our account mutexes, each mutex would boost the priority of its owner only when a high priority thread is waiting. This would give our high priority deposit threads a better chance to access accounts. The scheduler continues to favor the deposit threads, and when a deposit thread is blocked by a low priority thread that is holding a required mutex, the mutex's priority inheritance policy ensures that the low priority thread gets a needed boost. As a result, the low priority thread can get its business done quickly, release the mutex, and get out of the way of our important deposit threads. The worst case would be when the deposit thread must wait for one in-progress operation on the account before it can start its transaction.
To associate the priority inheritance protocol with our mutexes, we'll change our server's initialization routine as shown in Example 4-29.
Example 4-29: Initializing Priority-Savvy Mutex in the ATM (mutex_priority.c)
.
.
.
pthread_mutex_attr_t mutexattr_prioinherit;
.
.
.
void atm_server_init(int argc, char **argv)
{;
      .
      .
      .
      pthread_mutexattr_setprotocol(&mutexattr_prioinherit, PTHREAD_PRIO_INHERIT);
        for (i = 0; i < MAX_NUM_ACCOUNTS; i++)
                  pthread_mutex_init(&account_mutex[i], &mutexattr_prioinherit);
      .
      .
      .
}

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback