16

I am learning about POSIX threads and I have come to the section on Thread Specific Data. The book does an excellent example using a file descriptor. However, I wanted to do the same example on my own, except this time using a global variable. However, I am having some difficulty fully grasping this concept.

What I want to do is the following:

  • Create a global integer
  • Declare a key for the global int

in main:

  • set global integer to be a value eg. 10
  • create a key for it without any clean up
  • create 4 threads and send them to execute thread_func
  • check if value is still 10, since threads only see a copy of it

in thread_func:

  • use pthread_setspecific(key,global variable) to create a local instance - not sure if I am interpreting this correctly
  • call a function - dosomething()
  • exit

in do_something

  • create a local pointer and assign it to pthread_getspecific(key) - this should get me a thread specific version of the global variable
  • change the value of what is stored at the local pointer to 2
  • exit

Here's the code:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUMTHREADS 4

pthread_key_t glob_var_key;
int glob_var;

void do_something()
{
    //get thread specific data
    int* glob_spec_var = (int*) pthread_getspecific(glob_var_key);
    printf("Thread %d glob_spec before mod value is %d\n", (unsigned int) pthread_self(), *glob_spec_var);
    *glob_spec_var = 2;
    printf("Thread %d glob_spec after mod value is %d\n", (unsigned int) pthread_self(), *glob_spec_var);
}

void* thread_func(void *arg)
{
    pthread_setspecific(glob_var_key, &glob_var);
    do_something();
    pthread_exit(NULL);
}

int main(void)
{
    pthread_t threads[NUMTHREADS];
    int i;
    glob_var = 10;
    pthread_key_create(&glob_var_key,NULL);
    printf("Main: glob_var is %d\n", glob_var);
    for (i=0; i < NUMTHREADS; i++)
    {
        pthread_create(&threads[i],NULL,thread_func,NULL);
    }

    for (i=0; i < NUMTHREADS; i++)
    {
        pthread_join(threads[i], NULL);
    }
    printf("Main: glob_var is %d\n", glob_var);

    return 0;
}

From what I understood, when you call pthread_getspecific, each thread is supposed to have its own unique memory address for the memory addresses - which I did not find to be the case here. I know I am not going about this correctly and when I did try to look at the memory addresses for each thread when doing getspecific, I saw the same memory address. Perhaps someone can point me to an example where they use a global variable (not file descriptors) and they have thread-specific usage in which threads look at it as a local variable.

Cal
  • 734
  • 1
  • 9
  • 25
  • 2
    You're not supposed to set the *same* address in each threads key. Each thread sets their *unique* data at common key during thread startup. See [this example](http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=%2Fapis%2Fusers_34.htm) for an additional example. When `do_something()` is fired, it will get the current threads specific data at the specified key. This allows common code to act on the current threads' data rather than maintaining it in some thread-to-data map yourself. – WhozCraig Feb 26 '13 at 22:46
  • Thanks for the example. I'm just trying to find a decent example that uses a global variable but threads can locally modify it and the changes can only be seen by that thread. This is just for practice. Please can you elaborate on the address part and if its not too much trouble, show me the right way? – Cal Feb 26 '13 at 22:51
  • Is it your hope that, when properly setup, each thread has its *own* variable stored in thread-local-storage (TLS)? The answer to that is crucial in knowing *exactly* what your end-goal is. If you want all threads to share the *same* global, then a mutex or critical-section is in order. So can *you* elaborate a little more =P ? – WhozCraig Feb 26 '13 at 22:56
  • Ohhh im so silly. I understand what I did wrong - that example is excellent. I completely misinterpreted this. They malloc for the structure per thread function and then setspecific - then call the next function so the function will see the data pertaining to that thread and will look like a local copy for each thread. Am I on the right track with that reasoning? – Cal Feb 26 '13 at 23:00
  • "Is it your hope that, when properly setup, each thread has its own variable stored in thread-local-storage (TLS)? The answer to that is crucial in knowing exactly what your end-goal is. If you want all threads to share the same global, then a mutex or critical-section is in order. So can you elaborate a little more =P ? – WhozCraig 3 mins ago" What I initially thought was that I create a global variable, and somehow getspecific and setspecific would create local copies of that variable per thread - I don't think that is what happens. – Cal Feb 26 '13 at 23:02
  • I think you get it now. I posted an update (stripping, actually) of your code to somewhat demonstrate this. The idea of TLS is its just a cookie based database that fetches a value *you* set for the specific running thread given the cookie. This allows code (like your `do_something()` to act on per-thread data without keeping special maps laying around or passing a boatload of variables through parameter stacks. It is actually a really nice feature. – WhozCraig Feb 26 '13 at 23:07

3 Answers3

21

This is not an answer, but a side note:

If you are working on Linux-specific code, you can use the __thread keyword. Essentially,

static __thread int counter = 5;

creates a different counter variable for each thread, initialized to value 5 whenever a new thread gets created. Such code is future-compatible with C11, since C11 standardized the same semantics using the _Thread_local keyword. This is much saner than the POSIX thread-specific functions (which have implementation-specific limits, and are quite cumbersome compared to the __thread keyword), on all architectures using C, except those that have declared C99 and later "standard non grata" (i.e., Microsoft).

See the Thread-Local Storage chapter in GCC documentation for details.

Darren Ng
  • 373
  • 5
  • 12
Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
20

The purpose of TLS (thread-local storage) is to provide an defined mechanism whereby code can retrieve thread-specific data stored in a database accessed by a all-threads-known shared key. Your code is storing the same data in TLS: the address of a single global variable). Therefore when a thread requests this data using the tls-key, they will all get back the same address.

I think you intend your code to do something like this:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NUMTHREADS 4

pthread_key_t glob_var_key;

void do_something()
{
    //get thread specific data
    int* glob_spec_var = pthread_getspecific(glob_var_key);
    printf("Thread %d before mod value is %d\n", (unsigned int) pthread_self(), *glob_spec_var);
    *glob_spec_var += 1;
    printf("Thread %d after mod value is %d\n", (unsigned int) pthread_self(), *glob_spec_var);
}

void* thread_func(void *arg)
{
    int *p = malloc(sizeof(int));
    *p = 1;
    pthread_setspecific(glob_var_key, p);
    do_something();
    do_something();
    pthread_setspecific(glob_var_key, NULL);
    free(p);
    pthread_exit(NULL);
}

int main(void)
{
    pthread_t threads[NUMTHREADS];
    int i;

    pthread_key_create(&glob_var_key,NULL);
    for (i=0; i < NUMTHREADS; i++)
        pthread_create(threads+i,NULL,thread_func,NULL);

    for (i=0; i < NUMTHREADS; i++)
        pthread_join(threads[i], NULL);

    return 0;
}

Output

Thread 2625536 before mod value is 1
Thread 741376 before mod value is 1
Thread 3162112 before mod value is 1
Thread 3698688 before mod value is 1
Thread 2625536 after mod value is 2
Thread 741376 after mod value is 2
Thread 3162112 after mod value is 2
Thread 3698688 after mod value is 2
Thread 2625536 before mod value is 2
Thread 741376 before mod value is 2
Thread 3162112 before mod value is 2
Thread 3698688 before mod value is 2
Thread 2625536 after mod value is 3
Thread 741376 after mod value is 3
Thread 3162112 after mod value is 3
Thread 3698688 after mod value is 3
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • seems that mean: glob_var_key is same address, but `pthread_setspecific()` `pthread_getspecific()` will operate on different (thread-specific) address – yurenchen Oct 14 '22 at 12:52
4

In general, what you're looking for is "thread local storage". Here are a few links:

Choosing pthread_getspecific() is correct. Here's a good example:

Please review the above example: I think it will point you to the problem ... or suggest a good alternative.

IMHO...

paulsm4
  • 114,292
  • 17
  • 138
  • 190