分享

Per-thread global variables

 serenayang001 2010-12-14

Per-thread global variables

A simple way to make your library cumbersome to use in a threaded environment, is the use of global variables. They often have to be locked and unlocked, which costs quite a bit of time. If the global variables aren’t neccessarily global in the process but shared between functions (a good example is the infamous errno variable, which is thread-local by the way), the posix specification gives you something useful: pthread_setspecific. With this and some other functions from libpthread you can make an errno-like thread-local global variable.

In a modern glibc, errno is no longer an extern int errno; but a macro:

#define errno (*__errno_location ())

the __errno_location function finds out the thread-local address where errno is stored. This does not need pthread_getspecific since the C runtime allocates memory for errno explicitely. For our purposes we need to do our own allocation. Of course this is not limited to integers. The only thing we store is a pointer, the C runtime does not care what the pointer points to.

So, let’s define our macro first:

#define my_var (*(__get_my_var_location()))

The __get_my_var_location function now creates the key when it’s first called. It also allocates memory when it is first called in a specific thread:

pthread_key_t __my_var_location;
pthread_once_t __my_var_init;

void __init_my_var(void) {
pthread_key_create(&__my_var_location, NULL);
}

int *__get_my_var_location(void) {
void *ptr;
(void) pthread_once(&__my_var_init, __init_my_var);
ptr = pthread_getspecific(__my_var_location);
if(!ptr) {
ptr = malloc(sizeof(my_var_t));
pthread_setspecific(__my_var_location, ptr);
}
return (int*)ptr;
}

That’s easy to use already, but having to type out 2 functions for every variable-like macro which you want to use this way is still a bit cumbersome, so why not let the C preprocessor do it for you? Here’s where it gets evil! You can download the code at the bottom as perthread.h and simply use the following in your code (here’s a full test):

#include
#include "perthread.h"

PER_THREAD(unsigned int, foo) /* NOTE: no semicolon! */
#define foo (*(__get_foo_location()))

static pthread_barrier_t barrier;
void *thread(void *arg) {
foo = (unsigned int)pthread_self();
printf("thread: %u foo: %un", (unsigned int)pthread_self(), foo);
pthread_barrier_wait(&barrier);
printf("thread: %u foo: %un", (unsigned int)pthread_self(), foo);
return NULL;
}

int main(int argc, char **argv) {
pthread_t t1, t2, t3;
void *res;

pthread_barrier_init(&barrier, NULL, 3);
pthread_create(&t1, NULL, thread, NULL);
pthread_create(&t2, NULL, thread, NULL);
pthread_create(&t3, NULL, thread, NULL);
pthread_join(t1, &res);
pthread_join(t2, &res);
pthread_join(t3, &res);

return 0;
}

perthread.h contains the following PER_THREAD macro (you have to imagine a backslash at the endo of each line, wordpress messes it up):

#define PER_THREAD(type, name)
static pthread_once_t __ ## name ## _init = PTHREAD_ONCE_INIT;
static pthread_key_t __ ## name ## _location;
void __init_ ## name (void) {
pthread_key_create(&__ ## name ## _location, free);
}
int *__get_ ## name ## _location(void) {
void *ptr;
(void) pthread_once(&__ ## name ## _init, __init_ ## name);
ptr = pthread_getspecific(__ ## name ## _location);
if(!ptr) {
ptr = malloc(sizeof(type));
pthread_setspecific(__ ## name ## _location, ptr);
}
return (type*)ptr;
}

This macro takes care of creating your initialization function and get_location function, all with the correct type and name. To see that it’s correct, run it through the C preprocessor.


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多