which means that any variable declared as static or extern, or in the process heap, may be read and written by all threads within the process. That has several important implications for code that wants to store 'persistent' data between a series of function calls within a thread:
• The value in a static or extern variable, or in the heap, will be the value last written by any thread. In some cases this may be what you want, for example, to maintain the seed of a pseudorandom number sequence. In other cases, it may not be what you want.
• The only storage a thread has that's truly 'private' are processor registers. Even stack addresses can be shared, although only if the 'owner' deliberately exposes an address to another thread. In any event, neither registers nor 'private' stack can replace uses of persistent static storage in non-threaded code.
So when you need a private variable, you must first decide whether all threads share the same value, or whether each thread should have its own value. If they share, then you can use static or extern data, just as you could in a single threaded program; however, you must synchronize access to the shared data across multiple threads, usually by adding one or more mutexes.
If each thread needs its own value for a private variable, then you must store all the values somewhere, and each thread must be able to locate the proper value. In some cases you might be able to use static data, for example, a table where you can search for a value unique to each thread, such as the thread's pthread_t. In many interesting cases you cannot predict how many threads might call the function—imagine you were implementing a thread-safe library that could be called by arbitrary code, in any number of threads.
The most general solution is to malloc some heap in each thread and store the values there, but your code will need to be able to find the proper private data in any thread. You could create a linked list of all the private values, storing the creating thread's identifier (pthread_t) so it could be found again, but that will be slow if there are many threads. You need to search the list to find the proper value, and it would be difficult to recover the storage that was allocated by terminated threads — your function cannot know when a thread terminates.
I New interfaces should not rely on implicit persistent storage!
When you are designing new interfaces, there's a better solution. You should require the caller to allocate the necessary persistent state, and tell you where it is. There are many advantages to this model, including, most importantly:
• In many cases, you can avoid internal synchronization using this model, and, in rare cases where the caller wishes to share the persistent state between threads, the caller can supply the needed synchronization.
• The caller can instead choose to allocate more than one state buffer for use within a single thread. The result is several independent sequences of calls to your function within the same thread, with no conflict.
The problem is that you often need to support
Thread-specific data allows each thread to have a separate copy of a variable, as if each thread has an array of thread-specific data values, which is indexed by a common 'key' value. Imagine that the bailing programmers are wearing their corporate ID badges, clipped to their shirt pockets (Figure 5.2). While the information is different for each programmer, you can find the information easily without already knowing which programmer you're examining.
The program creates a
FIGURE 5.2
5.4.1 Creating thread-specific data
pthread_key_t key;
int pthread_key_create (
pthread_key_t *key,
void (*destructor)(void *));
int pthread_key_delete (pthread_key_t key);
A thread-specific data key is represented in your program by a variable of type pthread_key_t
. Like most Pthreads types, pthread_key_t is
If you need to create a thread-specific data key later, you have to ensure that pthread_key_create is called only once for each pthread_key_t variable. That's because if you create a key twice, you are really creating two different keys. The second key will overwrite the first, which will be lost forever along with the values any threads might have set for the first key.
When you can't add code to main, the easiest way to ensure that a thread-specific data key is created only once is to use pthread_once
, the one-time initialization function, as shown in the following program, tsd_once.c.
7-10 The tsd_t
structure is used to contain per-thread data. Each thread allocates a private tsd_t
structure, and stores a pointer to that structure as its value for the thread-specific data key tsd_key
. The thread_id member holds the thread's identifier (pthread_t
), and the string member holds the pointer to a 'name' string for the thread. The variable tsd_key
holds the thread-specific data key used to access the tsd_t
structures.
19-27 One-time initialization (pthread_once
) is used to ensure that the key tsd_key is created before the first access.
33-56 The threads begin in the thread start function thread_routine. The argument (arg) is a pointer to a character string naming the thread. Each thread calls pthread_once to ensure that the thread-specific data key has been created. The thread then allocates a tsd_t structure, initializes the thread_id member with the thread's identifier, and copies its argument to the string member.
The thread gets the current thread-specific data value by calling pthread_ getspecific, and prints a message using the thread's name. It then sleeps for a few seconds and prints another message to demonstrate that the thread-specific data value remains the same, even though another thread has assigned a different tsd_t structure address to the same thread-specific data key.
¦ tsd_once.c
1 #include <pthread.h>
2 #include 'errors.h'
3
4 /*
5 * Structure used as the value for thread-specific data key.
6 */
7 typedef struct tsd_tag {
8 pthread_t thread_id;
9 char *string;
10 } tsd_t;
11
12 pthread_key_t tsd_key; /* Thread-specific data key */
13 pthread_once_t key_once = PTHREAD_ONCE_INIT; 14