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.

Cancellation
Cancellation allows one thread to terminate another. One reason you may want to cancel a thread is to save system resources (such as CPU time) when your program determines that the thread's activity is no longer necessary. In an odd sense, you can consider cancellation to be a very rough synchronization mechanism: after you've canceled a thread, you know exactly where it is in its execution! A simple example of a thread you might want to cancel would be a thread performing a read-only data search. If one thread returns the results you are looking for, all other threads running the same routine could be canceled.
Okay, so you've decided that you'd like to cancel a thread. Now you must reckon whether the thread you've targeted can be canceled at all. The ability of a thread to go away or not go away when asked by another thread is known as its cancelability state. Let's assume that you can indeed cancel this thread. Now you must consider when it might go away—maybe immediately, maybe a bit later. The degree to which a thread persists after it has been asked to go away is known as its cancelability type. Finally, some threads are able to perform some special cleanup processing as part of being terminated (either through cancellation or through a pthread_exit call). These threads have an associated cleanup stack.
We'll get into cancelability states, cancel ability types, and cleanup stacks a little bit later (probably not late enough for those of you who winced at the use of the term cancelability).Right now, remember that threads don't have a parent/child relationship as processes do. So, any thread can cancel any other thread, as long as the canceling thread has the thread handle of its victim. Because you want your application to be solidly structured, you'll cancel threads only from the thread that initially created them.
The Complication with Cancellation
Cancellation is not as convenient as you might think at first. Most tasks that make multithreading worthwhile involve taking thread-shared data through some intermediate states before bringing it to some final state. Any thread accessing this data must take and release locks, as appropriate, to maintain proper synchronization. If a thread is to be terminated in the middle of such a prolonged operation, you must first release its locks to prevent deadlock. Often, you must also reset the data to some correct or consistent state. A good example of this would be fixing forward or backward pointers that a thread may have left hanging in a linked list.
For this reason, you must use cancellation very carefully. The simplest approach is to restrict the use of cancellation to threads that execute only in simple routines that do not hold locks or ever put shared data in an inconsistent state. Another option is to restrict cancellation to certain points at which a thread is known to have neither locks nor resources. Lastly, you could create a cleanup stack for the thread that is to be canceled; it can then use the cleanup stack to release locks and reset the state of shared data.
These options are all well and good when you are in charge of all the code your threads might execute. What if your threads call library routines that you don't control? You may have no idea of the detailed operation of these interfaces. One solution to this problem is to create cancellation-safe library routines, a topic we'll defer to the next chapter along with other issues of integration into a UNIX environment.
Cancelability Types and States
Because canceling a thread that holds locks and manipulates shared data can be a tricky procedure, the Pthreads standard provides a mechanism by which you can set a given thread's cancel ability (that is, its ability to allow itself to be canceled). In short, a thread can set its cancel ability state and cancel ability type to any of the combinations listed in Table 4-1, thereby ensuring that it can safely obtain locks or modify shared data when it needs to.
A thread can switch back and forth any number of times across the various permitted combinations of cancel ability state and type. When a thread holds no locks and has no resources allocated, asynchronous cancellation is a valid option. When a thread must hold and release locks, it might temporarily disable cancellation altogether.
Note that the Pthreads standard gives you no attribute that would allow you to set a thread's cancel ability state or type when you create it. A thread can set its own cancel ability only at run time, dynamically, by calling into the Pthreads library.
Table 4-1: Cancelability of a Thread
Cancelability State
Cancelability Type
Description
PTHREAD_ CANCEL_ DISABLE
Ignored
Disabled. The thread can never be canceled. Calls to pthread_cancel have no effect. The thread can safely acquire locks and resources.
PTHREAD_ CANCEL_ ENABLE
PTHREAD_ CANCEL_ ASYNCHRONOUS
Asynchronous cancellation. Cancellation takes effect immediately.*
PTHREAD_ CANCEL_ ENABLE
PTHREAD_ CANCEL_ DEFERRED
Deferred cancellation (the default). Cancellation takes effect only if and when the thread enters a cancellation point. The thread can hold and release locks but must keep data in some consistent state. If a pending cancellation exists at a cancellation point, the thread can terminate without leaving problems behind for the remaining threads.
 *The Pthreads standard states that cancellation will take place "at any time" We trust that most implementations interpret this phrase to mean "as soon as possible" The thread must avoid taking out locks and performing sensitive operations on shared data.
Cancellation Points: More on Deferred Cancellation
When a thread has enabled cancellation (that is, it has set its cancel ability state to PTHREAD_CANCEL_ENABLE) and is using deferred cancellation (that is, it has set its cancel ability type to PTHREAD_CANCEL_DEFERRED), time can elapse between the time it's asked to cancel itself and the time it's actually terminated.
These pending cancellations are delivered to a thread at defined locations in its code path. These locations are known as cancellation points, and they come in two flavors:
 Automatic cancellation points (pthread_cond_wait,pthread_cond_timedwait, and pthread_join). The Pthreads library defines these function calls as cancellation points because they can block the calling thread. Rather than maintain the overhead of a blocked routine that's destined to be canceled, the Pthreads library considers these calls to be a license to kill the thread. Note that, if the thread for which the cancellation is pending does not call any of these functions, it may never actually be terminated. This is one of the reasons you may need to consider using a programmer-defined cancellation point.
 Programmer-defined cancellation points (pthread_testcancel).To force a pending cancellation to be delivered at a particular point ina thread's code path, insert a call to pthread_testcancel. The pthread_testcancel function causes any pending cancellation to be delivered to the thread at the program location where it occurs. If no cancellation is pending on the thread, nothing happens. Thus, you can freely insert this call at those places in a thread's code path where it's safe for the thread to terminate. It's also prudent to call pthread_testcancel before a thread starts a time-consuming operation. If a cancellation is pending on the thread, it's better to terminate it as soon as possible, rather than have it continue and consume system resources needlessly.
The Pthreads standard also defines cancellation points at certain standard system and library calls. We'll address this topic in Chapter 5, Pthreads and UNIX.
A Simple Cancellation Example
Example 4-14 illustrates the basic mechanics of cancellation. The main routine creates three threads: bullet_proof, ask_for_it, and sitting_duck. Each thread selects a different cancellation policy: the bullet_proof routine disables cancellation, the ask_for_it routine selects deferred cancellation, and the sitting_duck routine enables asynchronous cancellation.
The main routine waits until all of the threads have started and entered an infinite loop. It then tries to cancel each thread with a pthread_cancel call. By issuing a join on each thread, it waits until all threads have terminated.
Example 4-14: The Simple Cancellation Example—main (cancel.c)
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#define NUM_THREADS 3
int count = NUM_THREADS;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t init_done=PTHREAD_COND_INITIALIZER;
int id_arg[NUM_THREADS] = {;0,1,2};
extern int
main(void)
{;
  int i;
  void *statusp;
  pthread_t threads[NUM_THREADS];
  /**** Create the threads ****/
  pthread_create(&(threads[0]), NULL, ask_for_it, (void *) &(id_arg[0]));
  pthread_create(&(threads[1]), NULL, sitting_duck, (void *) &(id_arg[1]));
  pthread_create(&(threads[2]), NULL, bullet_proof, (void *) &(id_arg[2]));
  printf("main(): %d threads created\n",count);
  /**** wait until all threads have initialized ****/
  pthread_mutex_lock(&lock);
  while (count != 0) {;
    pthread_cond_wait(&init_done, &lock);
  }
  pthread_mutex_unlock(&lock);
  printf("main(): all threads have signaled that they're ready\n");
  /**** cancel each thread ****/
  for (i = 0; i < NUM_THREADS; i++) {;
    pthread_cancel(threads[i]);
  }
  /**** wait until all threads have finished ****/
  for (i = 0; i < NUM_THREADS; i++) {;
    pthread_join(threads[i], &statusp);
    if (statusp == PTHREAD_CANCELED) {;
       printf("main(): joined to thread %d, statusp=PTHREAD_CANCELED\n", i);
    } else {;
       printf("main(): joined to thread %d \n", i);
    }
  }
  printf("main(): all %d threads have finished. \n", NUM_THREADS);
  return 0;
}
The bullet_proof thread: no effect
When a thread, like bullet_proof, disables  cancellation, it is impervious to pthread_cancel calls from other threads, as shown in Example 4-15.
Example 4-15: The Simple Cancellation Example—bullet_proof (cancel.c)
void *bullet_proof(int *my_id)
{;
  int i=0, last_state;
  char *messagep;
  messagep = (char *)malloc(MESSAGE_MAX_LEN);
  sprintf(messagep, "bullet_proof, thread #%d: ", *my_id);
  printf("%s\tI'm alive, setting general cancelability OFF\n", messagep);
  /* We turn off general cancelability here */
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &last_state);
  pthread_mutex_lock(&lock);
  {;
  printf("\n%s signaling main that my init is done\n", messagep);
  count -= 1;
  /* Signal to program that loop is being entered */
  pthread_cond_signal(&init_done);
  pthread_mutex_unlock(&lock);
  }
  /* Loop forever until picked off with a cancel */
  for(;;i++) {;
    if (i%10000 == 0)
      print_count(messagep, *my_id, i);
    if (i%100000 == 0)
      printf("\n%s This is the thread that never ends... #%d\n", messagep, i);
  }
  /* Never get this far */
  return(NULL);
}
The bullet_proof thread calls pthread_setcancelstate  to set its cancelability state to disabled (PTHREAD_CANCEL_DISABLE). After it enters its loop, it repeatedly taunts main until the program ends. Because the main thread has  issued a pthread_join call to wait on the bullet_proof  thread, we'll need to shoot the whole program with a  CTRL-C to get bullet_proof to stop.
The ask_for_it thread: deferred cancellation
The ask_for_it thread calls pthread_setcancelstate to set its cancelability state to enabled (PTHREAD_CANCEL_ENABLE) and pthread_setcanceltype to set its cancelability type to deferred (PTHREAD_CANCEL_DEFERRED). (It actually didn't need to explicitly do so, as deferred cancellation is the default for all threads.) After main has issued a pthread_cancel for it, the ask_for_it thread terminates when it enters the next cancellation point, as shown in Example 4-16.
Example 4-16: The Simple Cancellation Example—ask_for_it (cancel.c)
void *ask_for_it(int *my_id)
{;
  int i=0, last_state, last_type;
  char *messagep;
  messagep = (char *)malloc(MESSAGE_MAX_LEN);
  sprintf(messagep, "ask_for_it, thread #%d: ", *my_id);
  /* We can turn on general cancelability here and disable async cancellation. */
  printf("%s\tI'm alive, setting deferred cancellation ON\n", messagep);
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &last_state);
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_type);
  pthread_mutex_lock(&lock);
  {;
  printf("\n%s signaling main that my init is done\n", messagep);
  count -= 1;
  /* Signal to program that loop is being entered */
  pthread_cond_signal(&init_done);
  pthread_mutex_unlock(&lock);
  }
  /* Loop forever until picked off with a cancel */
  for(;;i++) {;
    if (i%1000 == 0)
      print_count(messagep, *my_id, i);
    if (i%10000 == 0)
      printf("\n%s\tLook, I'll tell you when you can cancel me.%d\n", messagep, i);
      pthread_testcancel();
  }
  /* Never get this far */
  return(NULL);
}
We'll force the delivery of main's cancellation request by adding a pthread_testcancel call to its loop. After main calls pthread_cancel, ask_for_it will terminate when it encounters pthread_testcancel in the next iteration of the loop.
The sitting_duck thread: asynchronous cancellation
The sitting_duck thread calls pthread_setcancelstate to set its cancelability state to enabled (PTHREAD_CANCEL_ENABLE) and pthread_setcanceltype to set its cancelability type to asynchronous (PTHREAD_CANCEL_ASYNCHRONOUS).When main issues a pthread_cancel for it, the sitting_duck thread terminates immediately, regardless of what it is doing.
If we leave our thread in this state, it can be canceled during library and system calls as well. However, unless these calls are documented as "asynchronous cancellation-safe," we should guard against this.(The Pthreads standard requires that only three routines be asynchronous cancellation-safe: pthread_cancel, pthread_setcanceltype, and pthread_setcancelstate.) If we don't, our thread could be canceled in the middle of such a call, leaving its call state in disarray and potentially messing up things for the other threads in the process. In Example 4-17, we'll protect the printf call against asynchronous cancellation by setting cancellation to deferred for the duration of the call. Note that the print_count routine called by the sitting_duck thread would also need to take this precaution before it makes library or system calls.
Example 4-17: The Simple Cancellation Example—sitting_duck (cancel.c)
void *sitting_duck(int *my_id)
{;
  int i=0, last_state, last_type, last_tmp;
  char messagep;
  messagep = (char *)malloc(MESSAGE_MAX_LEN);
  sprintf(messagep, "sitting_duck, thread #%d: ", *my_id);
  pthread_mutex_lock(&lock);
  {;
    printf("\n%s signaling main that my init is done\n", messagep);
    count -= 1;
    /* Signal to program that loop is being entered */
    pthread_cond_signal(&init_done);
    pthread_mutex_unlock(&lock);
  }
  /* Now, we're safe to turn on async cancelability */
  printf("%s\tI'm alive, setting async cancellation ON\n", messagep);
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &last_type);
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &last_state);
  /* Loop forever until picked off with a cancel */
  for(;;i++) {;
    if (i%1000) == 0)
      print_count(messagep, *my_id, i);
    if (i%10000 == 0) {;
      pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_tmp);
      printf("\n%s\tHum, nobody here but us chickens. %d\n", messagep, i);
      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &last_tmp);
      }
  }
  /* Never get this far */
  return(NULL);
}
When the sitting_duck thread has asynchronous cancellation enabled, it is canceled when main requests its cancellation—whether it's blocked by the scheduler or in the middle of its print_count loop.
Cleanup Stacks
Pthreads associates a cleanup stack with each thread. The stack allows a thread to do some final processing before it terminates. Although we're discussing cleanup stacks as a way to facilitate a thread's cancellation, you can also use cleanup stacks in threads that call pthread_exit to terminate themselves.
A cleanup stack contains pointers to routines to be executed just before the thread terminates. By default the stack is empty; you use pthread_cleanup_push to add routines to the stack, and pthread_cleanup_pop to remove them. When the library processes a thread's termination, the thread executes routines from the cleanup stack in last-in first-out order.
We'll adjust Example 4-17 to show how cleanup stacks work. We'll keep the main routine the same but have it start all the threads it creates in the sitting_duck routine. We'll change sitting_duck so that it uses the cleanup stack of the thread in which it is executing. Finally, we'll create a new routine, last_breath, so that our threads have something they can push on the stack. The sitting_duck routine calls pthread_cleanup_push to put the last_breath routine on top of the thread's cleanup stack. At its end, it calls pthread_cleanup_pop to remove the routine from the stack, as shown in Example 4-18.
Example 4-18: Cleanup Stacks—last_breath and sitting_duck (cancel.c)
/*
* Cleanup routine: last_breath
*/
void last_breath(char *messagep)
{
  printf("\n\n%s last_breath cleanup routine: freeing 0x%x\n\n", messagep,
          messagep);
  free(messagep);
}
/*
* sitting_duck routine
*/
void *sitting_duck(int *my_id)
{
  int i=0, last_state, last_type, last_tmp;
  char *messagep;
  messagep = (char *)malloc(MESSAGE_MAX_LEN);
  sprintf(messagep, "sitting_duck, thread #%d: ", *my_id);
  /* Push last_breath routine onto stack */
  pthread_cleanup_push((void *)last_breath, (void *)messagep);
  pthread_mutex_lock(&lock);
  {
  printf("\n%s signaling main that my init is done\n", messagep);
  count -= 1;
  /* Signal program that loop is being entered */
  pthread_cond_signal(&init_done);
  pthread_mutex_unlock(&lock);
  }
printf("%s\tI'm alive, setting general cancelability ON, async cancellation
  ON\n", messagep);
  /* Now we're safe to turn on async cancelability */
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &last_state);
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &last_type);
/* Loop forever until picked off with a cancel */
  for(;;i++) {;
    if (i%1000) == 0)
      print_count(messagep, *my_id, i);
    if (i%10000 == 0) {;
      pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_tmp);
      printf("\n%s\tHum, nobody here but us chickens. %d\n", messagep, i);
      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &last_tmp);
    }
  }
  /* Never get this far */
  return(NULL);
  /* This pop is required by the standard, every push must
     have a pop in the same lexical block. */
  pthread_cleanup_pop(0);
}
Other cleanup routines might perform additional tasks, such as resetting shared resources to some consistent state, freeing resources the thread still has allocated, and releasing the locks the thread still holds. We can design our own cleanup routines or simply use standard library calls like pthread_mutex_unlock or free if they would suffice.
There are a few more things about the pthread_cleanup_pop function you should know. First, pthread_cleanup_pop takes a single argument—an integer that can have either of two values:
 If the value of this argument is 1, the thread that called pthread_cleanup_pop executes the cleanup routine whose pointer is being removed from the cleanup stack. Afterwards, the thread resumes at the line following its pthread_cleanup_pop call. This allows a thread to execute a cleanup routine whether or not it is actually being terminated.
 If the value of this argument is 0, as it is in Example 4-18, the pointer to the routine is popped off the cleanup stack, but the routine itself does not execute.
Second, the Pthreads standard requires that there be one pthread_cleanup_pop for each pthread_cleanup_push within a given lexical scope of code. (Lexical scope refers to the code within a basic block of a C program—that set of instructions bounded by the curly braces { and }.) Why is this required? After all, the pthread_cleanup_pop function call we planted in sitting_duck occurs after an infinite loop and is never called. The reason is that this requirement makes it easier for Pthreads library vendors to implement cleanup routines. The pthread_cleanup_push and pthread_cleanup_pop function calls are easily and commonly implemented as macros that define the start and end of a block. Picture the pthread_cleanup_push routine as a macro that ends with an open curly brace ( { ) and the pthread_cleanup_pop routine as a macro that begins with a close curly brace ( } ). It's not hard to see why a C compiler would complain if we omitted the pthread_cleanup_pop call.
Cancellation in the ATM Server
The worker threads in our ATM server are likely candidates for cancellation. There are a couple of reasons why we might want to terminate a worker that is processing an account request:
 To allow a customer to abort a transaction that is in progress
 To allow the system to abort a transaction for security reasons or when it is shutting down
Remember that our worker threads do hold locks and do manipulate shared data—accounts in the bank's database. Dealing with the possibility of cancellation in our worker threads will have some interesting challenges.
In the remainder of this discussion, we'll focus on those changes to the server required to make its worker threads cancelable, without worrying about how the cancellation requests are generated. As a general model for a thread performing any type of request, we'll look at how a worker thread processes a deposit request.
Aborting a deposit
The basic steps a worker thread performs in processing a deposit request are shown in the following pseudocode:
1  process_request
2            switch based on transaction type to deposit()
3            deposit()
4                          parse request arguments
5                          check arguments
6                          lock account mutex
7                          retrieve account from database
8                          check password
9                          modify account to add deposit amount
10                         store modified account with database
11                         unlock account mutex
12          send response to client
13          free request buffer
14 return and implicit termination
Up to Step 5, the thread would have little difficulty accommodating a cancellation request and terminating. After Step 5, it performs some tasks that make us consider ways in which it must respond to cancellation:
 At Step 6, the thread obtains a lock on an account. At this moment, it must ensure somehow that, if it is the victim of cancellation, it can release the lock so that other threads can use the account after its demise. We can handle this from a cleanup routine that we'll push onto the cleanup stack.
 At Step 10, the thread commits a change to the account but has yet to send an acknowledgment to the client. Let's assume that, after we commit a change to an account, we want to make every effort to send a "transaction completed" response to the client. We'll give the thread a chance to do this by having it turn off cancellation before it writes anew balance. From that point to its termination at the end of process_request, it cannot be canceled.
 At Step 13, the thread frees the request buffer. The buffer was originally allocated by the boss thread, which passed it to the worker as an argument to the process_request routine. Because the boss does not save its pointer to this buffer, the worker is the only thread that knows where in the heap the buffer resides. If the worker doesn't free the buffer, nothing will. This is another chore we'll assign to the cleanup routine.
We'll rewrite our process_request and deposit routines to illustrate these changes in Example 4-19.We'll tackle process_request first. Note that, by default, threads starting in process_request will have deferred cancellation enabled.
Example 4-19: Changes to process_request for Cancellation (atm_svr_cancel.c)
void process_request(workorder_t *workorderp)
    {;
      char resp_buf[COMM_BUF_SIZE];
      int  trans_id;
      /**** Deferred cancellation is enabled by default ****/
      pthread_cleanup_push((void *)free, (void *)workorderp);
      sscanf(workorderp->req_buf, "%d", &trans_id);
      pthread_testcancel();
      switch(trans_id) {;
      case CREATE_ACCT_TRANS:
          create_account(resp_buf);
          break;
      case DEPOSIT_TRANS:
          deposit(workorderp->req_buf, resp_buf);
          break;
      case WITHDRAW_TRANS:
          withdraw(workorderp->req_buf, resp_buf);
          break;
      case BALANCE_TRANS:
          balance(workorderp->req_buf, resp_buf);
          break;
      default:
          handle_bad_trans_id(workorderp->req_buf, resp_buf);
          break;
      }
      /* Cancellation may be disabled by the time we get here, but this
         won't hurt either way. */
      pthread_testcancel();
      server_comm_send_response(workorderp->conn, resp_buf);
      pthread_cleanup_pop(1);
    }
This version of process_request starts by calling pthread_cleanup_push to place a pointer to the free system routine at the top of the thread's cleanup stack. It passes a single parameter to free—the address of its request buffer. We've placed a matching call to pthread_cleanup_pop at the end of process_request. We pass pthread_cleanup_pop an argument of1 so that free will run and deallocate the buffer regardless of whether or not the thread is actually canceled. If the thread is canceled, the buffer will be freed before it terminates; if not, the buffer will be freed at the pthread_cleanup_pop call.
We'll now look at the changes to deposit in Example 4-20.
Example 4-20: A Cancelable ATM Deposit Routine (atm_svr_cancel.c)
void deposit(char *req_buf, char *resp_buf)
{;
  int rtn;
  int temp, id, password, amount, last_state;
  account_t *accountp;
  /* Parse input string */
  sscanf(req_buf, "%d %d %d %d ", &temp, &id, &password, &amount);
  .
  .
  .
  pthread_testcancel();
  pthread_cleanup_push((void *)pthread_mutex_unlock, (void *)&account_mutex[id]);
  pthread_mutex_lock(&account_mutex[id]);
  /* Retrieve account from database */
  rtn = retrieve_account( id, &accountp);
  .
  .
  .
  pthread_testcancel();
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &last_state);
  /* Store back to database */
  if ((rtn = store_account(accountp)) < 0) {;
  .
  .
  .
  pthread_cleanup_pop(1);
}
This version of the deposit routine pushes the address of the pthread_mutex_unlock function on to the thread's cleanup stack before calling pthread_mutex_lock to obtain the mutex. As we did in the process_request routine, we've placed a matching call to pthread_cleanup_pop at the end of deposit. We pass pthread_cleanup_pop an argument of 1 so that pthread_mutex_unlock will be run at the pthread_cleanup_pop call, if the thread is not previously terminated and the mutex unlocked, as the result of a cancellation request.
Because deferred cancellation is enabled for the thread, we can be sure that it can be cancelled only at a cancellation point. However, if there were a cancellation point between the calls to pthread_cleanup_push and pthread_mutex_lock we could get into trouble. If our thread were cancelled at that time, the cleanup would try to unlock a mutex that hasn't yet been locked! The consequences of such extravagance are undefined by the Pthreads standard, so we most surely want to avoid them. Our code is safe because there's no such cancellation point between the calls. For the same reason, the order in which we make the calls is immaterial.
Let's see what this means for our process_request routine. Remember that the request buffer was allocated by the boss thread and passed to the worker thread in the pthread_create call. Even though the new thread executing process_request immediately pushes the address of free on to its cleanup stack, its push inarguably happens sometime after the boss performed the initial malloc. Is this a case of too little too late?
Not necessarily. In our example of cancellation, the boss thread implicitly hands off responsibility for the request buffer to the worker thread that's executing process_request. The boss thread knows for certain that process_request is the first routine any newly created worker thread will run. By default, all threads are created with deferred cancellation enabled, and this is the cancelability type of the thread at the time it pushes the address of free onto the stack. If it doesn't encounter a cancellation point before we push free on the cleanup stack, there's no exposure. However, because some system and library calls contain cancellation points, a thread is best off when it expects to be canceled at any time. If any of your code relies on a particular thread not having any cancellation points, be sure to include a comment to that effect.
Just before the deposit routine writes the new balance to the account database, it disables cancellation by calling pthread_setcancelstate. Subsequently, the thread can complete the deposit routine without fear of cancellation. In fact, when the thread exits the deposit and returns to process_request, cancellation is still disabled.
We've made a lot of changes to our process_request and deposit routines to allow other threads to cancel a worker thread in the middle of a deposit request. Each change adds overhead to the real work of our ATM server. These safeguards against unexpected cancellation are charged against the performance of a thread each time it executes process_request or deposit, not just when it's destined to be canceled. Consequently, we should carefully consider whether making our threads cancelable is worth the extra performance cost. If the threads in question run for only a short period of time before exiting, the complexity is hardly worthwhile. However, if the threads run for a long period of time and consume many system resources, the performance gains of a cancellation policy may certainly outweigh its inevitable overhead.
Following this line of reasoning, the Pthreads standard defines most blocking system calls, plus many others that can take a long time to execute, as cancellation points. Some implementations may include other library and system calls. See your platform's documentation for information on exactly which calls it defines as cancellation points.

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