#include <pthread.h> int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *)); int pthread_key_delete(pthread_key_t key); /* 两个函数返回值:若成功,返回 0;否则,返回错误编号 */
创建的键存储在 keyp 指向的内存单元中,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。创建新键时,每个线程的数据地址都设为空值。除了创建键以外,pthread_key_create 还可以为该键关联一个可选择的析构函数。当这个线程退出时,如果数据地址已经被置为非空值,那么析构函数就会被调用,它唯一的参数就是该数据地址。当线程调用 pthread_exit 或者返回时,析构函数就会被调用;而当线程取消时,只有在最后的清理处理程序返回后,析构函数才会被调用。线程通常使用 malloc 为线程特定数据分配内存,所以析构函数经常调用 free 函数来释放已分配的内存,以免产生内存泄露。线程可以为线程特定数据分配多个键,每个键都可以关联一个析构函数。线程退出时,线程特定数据的析构函数将按照操作系统实现中定义的顺序被调用。当所有的析构函数都调用完成后,系统会检查是否还有非空的线程特定数据值与键关联。如果有的话,会再次调用析构函数。该过程会一直重复直到线程所有的键都为空线程特定数据值,或者已经做了 PTHREAD_DESTRUCTOR_ITERATIONS 次尝试。
调用 pthread_key_delete 并不会激活与键关联的析构函数,要释放任何与键关联的线程特定数据值的内存,需要在应用程序中采取额外的步骤。
为确保分配的键不会因初始化阶段的竞争而发生变动,可以使用函数 pthread_once。键一旦创建以后,就可以通过 pthread_setspecific 函数把键和线程特定数据关联起来,也可通过 pthread_getspecific 函数获得线程特定数据的地址。
#include <pthread.h> pthread_once_t initflag = PTHREAD_ONCE_INIT; int pthread_once(pthread_once_t *initflag, void (*initfn)(void)); int pthread_setspecific(pthread_key_t key, const void *value); /* 两个函数返回值:若成功,返回 0;否则,返回错误编号 */ void *pthread_getspecific(pthread_key_t key); /* 返回值:线程特定数据值;若没有值与该键关联,返回 NULL */
这里的 initflag 必须是一个非本地变量(如全局变量或静态变量),而且必须初始化为 PTHREAD_ONCE_INIT。就算每个线程都调用 pthread_once,系统也能保证初始化例程 initfn 只被调用一次,这可以避免创建键时出现冲突。
下面给出了 getenv 的一个假设实现,它使用线程特定数据来维护每个线程的数据缓冲区副本,用于存放各自的返回字符串。
#include <stdlib.h> #include <string.h> #include <pthread.h> static pthread_key_t key; static pthread_once_t init_done = PTHREAD_ONCE_INIT; static pthread_mutex_t env_mutex = PTHREAD_MUTEX_INITIALIZER; static void key_init(void){ pthread_key_create(&key, free); } #define MAXSTRINGSZ 4096 extern char **environ; char *getenv(const char *name){ pthread_once(&init_done, key_init); pthread_mutex_lock(&env_mutex); char *env_buf = NULL; if((env_buf=(char *)pthread_getspecific(key)) == NULL){ if((env_buf=malloc(MAXSTRINGSZ)) == NULL){ pthread_mutex_unlock(&env_mutex); return NULL; } pthread_setspecific(key, env_buf); } int i = 0, len = strlen(name); for(; environ[i] != NULL; i++){ if(strncmp(name, environ[i], len)==0 && environ[i][len]=='='){ strncpy(env_buf, &environ[i][len+1], MAXSTRINGSZ-1); pthread_mutex_unlock(&env_mutex); return env_buf; } } pthread_mutex_unlock(&env_mutex); return NULL; }
这里使用 pthread_once 来确保只为将要使用的线程特定数据创建一个键。如果 pthread_getspecific 返回的是空指针,就需要先分配内存缓冲区,然后再把键与该内存缓冲区关联起来,否则就使用返回的内存缓冲区。对析构函数,使用 free 来释放之前由 malloc 分配的内存。只有当线程特定数据值为非空时,析构函数才会被调用。
注意,相较于 线程重入中实现的 getenv_r 版本,虽然该版本的 genenv 也使用了互斥量来保证线程安全,但它并不是异步信号安全的。对信号处理程序而言,即使使用递归的互斥量,该版本也不可能是可重入的,因为它调用了本身就不是异步信号安全的 malloc 函数。