Caution: in the following, I haven't even compiled any of the code, much less tested it. Use it at your own risk.
Right. Where was I? Oh, yes. Harrumph.
Everyone knows what global variables are. But suppose you want a variable that's almost global; that is, suppose you want a "global" variable that has a
unique instance for each thread.
Let's explore a typical use of this. Suppose you want to write a library of C functions which are thread safe and have a "global" variable, one such global variable per thread. You might want a global variable to keep track of the state of your library between one call to it and the next, say.
Suppose, say, you're defining a function
fred(). The first time
fred() is called by the end user, you want to initialize some data, and each time after that you want to be able to see that data and perhaps modify it. This data should be kept separate for each thread.
Remember, you can't change the code that's calling
fred();
fred() is a library function, remember?
Ok, let's make it even more complex. Suppose you want to maintain two such global sets of data (with one instance per thread per set). Under some conditions you'll want to initialize both of them, sometimes just one or the other, sometimes none of them.
Let's see how you'll do this.
Let's call the two sets of data
wilma and
betty. For purposes of simplicity (for now), let's say that each set of data is a
struct of some sort.
The first step, to be done
only once for betty and once for wilma while the program is running, not once per thread, is to get a key for
wilma and a key for
betty. To get a key, use
pthread_key_create(). Store the value for the key in a global variable. (You might actually call that variable
wilma or
betty.)
You'll notice that the second parameter to the function
pthread_key_create() is the name of a destructor function. What's that all about?
Suppose that each time you create an instance of
wilma (at most once per thread, remember?), your code will first do a
malloc() to get the memory for the
struct. At some time you want to give that memory back; this will typically be at the time that the thread exits. The function that will be used to give the memory back is this destructor function. You know, at the time the program starts and you get a key for
wilma, that each instance of
wilma will be the result of a
malloc() call, so when you get the key, you specify
free() as the destructor function.
Suppose, though, that
betty is more complex. Suppose that it's a
struct containing a field that's a pointer to another
struct . Suppose that each thread, when initializing its instance of
betty, will
malloc() the
struct for betty, and then also
malloc() another
struct, a pointer to which is stored inside that instance of
betty.
In that case, specifying
free() as the destructor function for each instance of
betty isn't good enough; you'll be losing (not freeing, just losing) that second chunk of memory. What you need is a function which frees that auxiliary chunk of memory first, and then frees the primary
betty struct. So write that function yourself, and specify it as the destructor function for
betty.
Here's a sample:
Code:
void bettyfree(void *bettychunk)
{
free(((struct bettystruct *)bettychunk)->secondchunk);
free(bettychunk);
} /* bettyfree() */
Having talked about the destructor function, let's go back to another question about
pthread_key_create(): How do you make sure that you run it only once per running of the whole program, and not once per thread? You're writing a library, remember, so you can't put the call to
pthread_key_create() in, say,
main().
The answer is
pthread_once().
So your library should define these global variables:
Code:
pthread_once_t mylib_once_var=PTHREAD_ONCE_INIT;
pthread_key_t mylib_wilma;
pthread_key_t mylib_betty;
You should have an initialization function in your library that looks something like this:
Code:
void mylib_init
{
if(pthread_key_create(&mylib_wilma,free())
{
/* put your error recovery code here */
}
if(pthread_key_create(&mylib_betty,bettyfree())
{
/* put your error recovery code here */
}
} /* mylib_init() */
Then, to be safe,
each function in your library should have code like this at its beginning, to make sure that the initialization code is called once and only once for the whole running of the program:
Code:
if(pthread_once(mylib_once_var,mylib_init))
{
/* put your error recovery code here */
}
Then each function which needs an instance of wilma for that thread should do this:
Code:
struct wilma pointer_to_my_wilma;
/* ... */
pointer_to_my_wilma=pthread_getspecific(mylib_wilma);
if(pointer_to_my_wilma==NULL)
{
/* We haven't set up an instance of wilma for this thread yet. */
pointer_to_my_wilma=malloc(sizeof(struct wilma));
if(pointer_to_my_wilma==NULL)
{
/* malloc() failed; do error recovery here */
}
/* We have just allocated space for wilma's instance for this thread.
Write code here which fills that space with your initializing data
for wilma for this thread.
*/
/* Then inform pthreads about this instance. */
if(pthread_setspecific(mylib_wilma,pointer_to_my_wilma))
{
/* put your error recovery code here */
}
}
Note that you don't need to execute the above code until you actually need wilma data for this thread. Some threads might never execute this code at all, if that's what you want.
Doing the same thing for betty is left as an exercise for the reader.
Now,
jork, I have two questions for you.
- What is this pthreads "manual" you are reading? If you wish to do any serious POSIX threads programming, then run (do not walk) to your nearest bookseller and order Programming with POSIX Threads, by David R. Butenhof.
The reason I recommend this book is that pthreads programming can involve you in hours and hours and HOURS of fun chasing down subtle bugs that
- are not repeatable; and
- give you symptoms of misbehavior in one thread where the actual bug lies in another.
These bugs can be simple C errors; they can also be failures to understand some of the implications of POSIX threads. Butenhof is an excellent guide to POSIX threads pitfalls.
My copy of an O'Reilly book on the same topic is, in theory, complete, but does not warn you of many of these pitfalls, so I'd stay away from it.
- Your location is beweth. Where is beweth?