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 it has references to two global variables, errno and mywindow, that 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. So, references to errno by one thread refer to a different storage location than references to errno by other threads.

The mywindow variable is intended to refer to a stdio stream 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 (and, ultimately, a different window) than references to mywindow by other threads. The only difference here is that the threads library takes care of errno, but the programmer must somehow make this work 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(), passing it the mywindow_key global variable (it really is a global variable) and an output parameter, win, 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;

    pthread_getspecific(mywin_key, &win);
    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; that is, 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 it.

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

Example 2-4 shows how to set this up.


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_keycreate(&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. So, the first thread to call make_mywin() eventually calls pthread_keycreate(), 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, a call is made to the create_window() routine, which sets up a window for the thread and sets the storage pointed to by win to refer to it. Finally, a call is made to pthread_setspecific(), which associates the value contained in win (that is, the location of the storage containing the reference to the window) with the key.

After this, whenever this thread calls pthread_getspecific(), passing the global key, it gets the value that was associated with this key by this thread when it called 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().