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.

The pthread_once Mechanism
When you create many threads that cooperate to accomplish a single task, you must sometimes perform a single operation up front so that all of these threads can proceed. For instance, you may need to open a file or initialize a mutex. Up to now, we've had our boss thread handle these chores, but that's not always feasible.
The pthread_once mechanism is the tool of choice for these situations. It, like mutexes and condition variables, is a synchronization tool, but its specialty is handling synchronization among threads at initialization time. If the pthread_once function didn't exist, we'd have to initialize all data, mutexes, and condition variables before we could create any thread that uses them. After our program has started and spawned its first thread, it would be very difficult for it to create new resources that require protection should some asynchronous event require that it do so.
If we're writing a library that can be called by a multithreaded application, this becomes more than just an annoyance. Perhaps we don't want (or can't have) a single function for our users to call that allows our library to initialize itself prior to its general use. Neither can we ask each of our library functions to first call an initialization routine. Remember, our library's multithreaded. How do we know whether or not another thread might be trying to initialize the same objects simultaneously?
Example: The ATM Server's Communication Module
Let's walk through an example that will help us illustrate the point. We'll use the communication module from our ATM server—that part of the server that receives requests from clients and unpacks them. The interface to the communication module is as shown in Example 4-6.
Example 4-6: Interface to the ATM Server Communication Module (atm_com_svr.c)
void server_comm_get_request(int *, char *);
void server_comm_send_response(int, char *);
void server_comm_close_conn(int);
void server_comm_shutdown(void);
Let's pretend that this is legacy code that we've been asked to incorporate into a multithreaded program. We'll also pretend that it contains an initialization routine and that we don't want to completely rewrite it to eliminate the routine.
The server_comm_get_request routine shown in Example 4-7 is typical of the interfaces in this module.
Example 4-7: Original server_comm_get_request Routine (atm_com_svr.c)
void server_comm_get_request(int *conn, char *req_buf)
{;
  int i, nr, not_done = 1;
  fd_set read_selects;
  if (!srv_comm_inited) {;
    server_comm_init();
    srv_comm_inited = TRUE;
  }
  /* loop, processing new connection requests until a client
     buffer is read in on an existing connection. */
  while (not_done) {;
  .
  .
  .
}
If the server_comm_inited flag is FALSE, theserver_comm_get_request routine calls an initialization routine (server_comm_init) and sets the flag to TRUE. If we allow multiple threads to call server_comm_init concurrently, we introduce a race condition on the srv_comm_inited flag and on all of server_comm_init's global variables and initializations. Consider: threads A and B enter the routine at the same time. Thread A checks the value of srv_comm_inited and finds FALSE. Thread B checks the value and also finds it FALSE. Then they both go forward and call srv_comm_init.
We'll consider two viable solutions:
 Adding a mutex to protect the srv_comm_inited flag and server_comm_init routine. Using PTHREAD_MUTEX_INITIALIZER, we'll statically initialize this mutex.
 Designating that the entire routine needs special synchronization handling by calling the pthread_once function.
Using a statically initialized mutex
If we choose to protect the srv_comm_inited flag and server_comm_init routine by a statically initialized mutex, our code would look like that in Example 4-8.
Example 4-8: The ATM with Static Initialization (atm_com_svr_init.c)
pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
void server_comm_get_request(int *conn, char *req_buf)
{;
  int i, nr, not_done = 1;
  fd_set read_selects;
  pthread_mutex_lock(&init_mutex)
  if (!srv_comm_inited) {;
    server_comm_init();
    srv_comm_inited = TRUE;
  }
  pthread_mutex_unlock(&init_mutex);
  /* loop, processing new connection requests until a client
     buffer is read in on an existing connection. */
  while (not_done) {;
    .
    .
    .
}
Using a statically defined mutex to protect the initialization flag and routine works in this simple case but has its drawbacks as a module grows more complex:
 When the initialization routine introduces dynamically allocated mutexes, it must initialize them dynamically. This is not an insurmountable problem; as long as at least one mutex is statically defined, it can control the initialization of all the other mutexes.
 The mutex protecting the initialization flag routine will continue to act as a synchronization point long after it is needed. Each time any thread enters the library, it will lock and unlock the mutex to read the flag and learn the old news: initialization is complete. (Using the pthread_oncefunction may also involve this type of overhead. However, because the purpose of the pthread_once call is known to the library, a clever library could optimize its use after initialization is complete.)
 You cannot define custom attributes for a statically initialized mutex. You can work around this problem, too; as long as at least one mutex is statically defined, it can control the initialization of all other mutexes that have custom attributes.
Using the pthread_once mechanism
If we use the server_comm_init routine only through the pthread_once mechanism, we can make the following synchronization guarantees:
 No matter how many times it is invoked by one or more threads, the routine will be executed only once by its first caller.
 No caller will exit from the <emphasis>pthread_once</emphasis> mechanism until the routine's first caller has returned.
To use the pthread_once mechanism, you must declare a variable known as a once block (pthread_once_t),and you must statically initialize it to the value PTHREAD_ONCE_INIT. The Pthreads library uses a once block to maintain the state of pthread_once synchronization for a particular routine. Note that we are statically initializing the once block to the PTHREAD_ONCE_INIT value. If the Pthreads standard allowed us to dynamically initialize it (that is, if the library defined a pthread_once_init call), we'd run into a race condition if multiple threads tried to initialize a given routine's once block at the same time.
In our ATM server, we'll call the once block srv_comm_inited_once and declare and initialize it globally:
pthread_once_t      srv_comm_inited_once = PTHREAD_ONCE_INIT;
Now that we've declared a once block, the server_comm_get_request routine no longer has to test a flag to determine whether to proceed with initialization. Instead, as shown in Example 4-9, it calls pthread_once, specifying the once block and the routine we've associated with it—server_comm_init.
Example 4-9: Using a Once Block in the ATM (atm_com_svr_once.c)
void server_comm_get_request(int *conn, char *req_buf)
{;
  int i, nr, not_done = 1;
  fd_set read_selects;
  pthread_once(&srv_comm_inited_once, server_comm_init);
  /* loop, processing new connection requests until a client
     buffer is read in on an existing connection. */
  while (not_done) {;
    .
    .
    .
}
We'll change the other interface routines in our ATM server's communication module in the same manner. Any number of threads can call into the module. Each interface call will initially involve a call to pthread_once, but only the first thread will actually enter server_comm_init and execute our module's initialization routine.
You can declare multiple once blocks in a program, associating each with a different routine. Be careful, though. Once you associate a routine with the pthread_once mechanism, you must always call it through a pthread_once call, using the same once block. You cannot call the routine directly elsewhere in your program without subverting the synchronization the pthread_once mechanism is meant to provide
Notice that the pthread_once interface does not allow you to pass arguments to the routine that is protected by the once block. If you're trying to fit a predefined routine with arguments into the pthread_once mechanism, you'll have to fiddle a bit with global variables, wrapper routines, or environment variables to get it to work properly.

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