Multithreaded Programming Guide

Global and Private Thread-Specific Data Example

Example 2–2 shows an excerpt from a multithreaded program. This code is executed by any number of threads, but the code has references to two global variables, errno and mywindow. These global values really should be references to items private to each thread.


Example 2–2 Thread-Specific Data—Global but Private

body() {
    ...

    while (write(fd, buffer, size) == -1) {
        if (errno != EINTR) {
            fprintf(mywindow, "%s\n", strerror(errno));
            exit(1);
        }
    }

    ...

}

References to errno should get the system error code from the routine called by this thread, not by some other thread. Including the header file errno.h causes a reference to errno to be a reference to a thread-private instance of errno, so that references to errno by one thread refer to a different storage location than references to errno by other threads.

The mywindow variable refers to a stdio stream that is connected to a window that is private to the referring thread. So, as with errno, references to mywindow by one thread should refer to a different storage location than references to mywindow by other threads. Ultimately, the reference is to a different window. The only difference here is that the system takes care of errno , but the programmer must handle references for mywindow .

The next example shows how the references to mywindow work. The preprocessor converts references to mywindow into invocations of the _mywindow() procedure.

This routine in turn invokes pthread_getspecific(). pthread_getspecific() receives the mywindow_key global variable and win an output parameter that receives the identity of this thread's window.


Example 2–3 Turning Global References Into Private References

thread_key_t mywin_key; 

FILE *_mywindow(void) {
 FILE *win;
 win = pthread_getspecific(mywin_key);
 return(win);
 }
#define mywindow _mywindow()

void routine_uses_win( FILE *win) {
 ... 
} 
void thread_start(...) {
 ... 
 make_mywin();
 ... 
 routine_uses_win( mywindow )
 ... 
}

The mywin_key variable identifies a class of variables for which each thread has its own private copy. These variables are thread-specific data. Each thread calls make_mywin() to initialize its window and to arrange for its instance of mywindow to refer to the thread-specific data.

Once this routine is called, the thread can safely refer to mywindow and, after _mywindow(), the thread gets the reference to its private window. References to mywindow behave as if direct references were made to data private to the thread.

Example 2–4 shows how to set up the reference.


Example 2–4 Initializing the Thread-Specific Data

void make_mywindow(void) {
    FILE **win;
    static pthread_once_t mykeycreated = PTHREAD_ONCE_INIT;

    pthread_once(&mykeycreated, mykeycreate);

    win = malloc(sizeof(*win));
    create_window(win, ...);

    pthread_setspecific(mywindow_key, win);
}

void mykeycreate(void) {
    pthread_key_create(&mywindow_key, free_key);
}

void free_key(void *win) {
    free(win);
}

First, get a unique value for the key, mywin_key. This key is used to identify the thread-specific class of data. The first thread to call make_mywin() eventually calls pthread_key_create() , which assigns to its first argument a unique key. The second argument is a destructor function that is used to deallocate a thread's instance of this thread-specific data item once the thread terminates.

The next step is to allocate the storage for the caller's instance of this thread-specific data item. Having allocated the storage, calling create_window() sets up a window for the thread. win points to the storage allocated for the window. Finally, a call is made to pthread_setspecific(), which associates win with the key.

Subsequently, whenever the thread calls pthread_getspecific() to pass the global key, the thread gets the value that is associated with this key by this thread in an earlier call to pthread_setspecific() .

When a thread terminates, calls are made to the destructor functions that were set up in pthread_key_create(). Each destructor function is called only if the terminating thread established a value for the key by calling pthread_setspecific().