线程特有数据(key)与线程局部存储(__thread)
要了解线程特有数据
与局部存储
的特性。首先需要了解什么是线程安全函数
,什么是非线程安全函数
。
线程安全函数
:函数可同时供多个线程安全调用.即可重入,函数重入时不会造成函数的逻辑混乱。非线程安全函数
:如果不是线程安全函数,那么他就是非线程安全函数。导致非安全的主要原因是函数使用了全局变量,内存分配等,在多线程并行访问此函数时,会造成此函数的逻辑错误。
将一个非线程安全函数转换为线程安全函数的方法很多,如:
使用互斥量
。对函数中的共享资源进行保护,保证函数逻辑的正确性。- 缺点:在线程遇到互斥量时,并行访问就转换成了顺序访问。
- 避免使用全局和静态变量等会造成函数不可重入的机制。
使用线程特有数据与局部存储
。一般用于库函数的编写中。
一般库函数的编写中,可能会用到线程特有数据与局部存储。
线程特有数据
线程特有数据:简单的说,就是为每个调用线程分别维护一份变量的副本。每个线程通过特有数据键访问时,这个特有数据键都会获取到本线程绑定的变量副本。
线程特有数据的使用:
- 首先通过pthread_key_create()函数创建初始化一个特有数据键,并绑定解构函数(线程在中止时调用,一般用于内存释放等)。
- 然后通过pthread_getspecific()绑定键与变量实体。
- 最后通过pthread_getspecific()函数获取变量实体并访问。
通过特有数据键来获取与访问。特有数据键是全局的。
int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
- 创建一个特有数据键。用于之后绑定特有数据。
- key:特有数据键实体指针。
- destructor:解构函数。线程终止时调用。
- return:
int pthread_setspecific(pthread_key_t key, const void *value)
- 绑定特有数据键与数据实体
- key:特有数据键
- value:数据内容实体。
void *pthread_getspecific(pthread_key_t key)
- 获取指定键对应的数据内容。如果未绑定数据实体。则返回NULL。可以利用这一点来判断自身是否是初次为某个线程所调用,若为初次,
则必须为该线程分配空间。 - 返回数据实体指针。如果未绑定数据实体。则返回NULL。
- 获取指定键对应的数据内容。如果未绑定数据实体。则返回NULL。可以利用这一点来判断自身是否是初次为某个线程所调用,若为初次,
众所周知,strerror()
是一个非线程安全函数。下面的例程展示了使用特有数据编写strerror()函数,使其成为线程安全函数.例程如下。
#define MAX_ERROR_LEN 256
static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;
/* 线程终止时会调用此函数进行内存释放 */
static void destructor(void *buf)
{
free(buf);
}
static void createKey(void)
{
/* 创建特有数据键 */
int s = pthread_key_create(&strerrorKey, destructor);
if (s != 0){
printf("key_create error.\n");
exit(1);
}
}
void my_strerror(int err)
{
int s;
char *buf;
/* 1. 只初始化一次->创建特有数据键 */
pthread_once(&once, createKey);
/* 2. 查看特有数据键对应的内存是否为空 */
buf = pthread_getspecific(strerrorKey);
if (buf == NULL) { //为空则分配内存
buf = malloc(MAX_ERROR_LEN);
if (buf == NULL){
exit(1);
}
/* 3. 给特有数据键绑定内存 */
if (pthread_setspecific(strerrorKey, buf) != 0){
printf("setspecific error.\n");
}
}
if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
} else {
strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
}
return buf;
}
线程局部存储
正常情况下我们用static定义的变量,会被存放到进程的初始化数据区,这导致各个线程都共享这个变量。而线程局部存储在定义变量时,使用__thread
修饰变量.此时,每个线程都会拥有一份对变量的拷贝。线程局部存储中的变量将一直存在,直至线程终止,届时会自动释放这一存储。
局部存储的使用更简单,只需要在定义变量时,使用__thread
修饰。如static __thread buf[MAX_ERROR_LEN];
.
使用线程局部存储实现strerror()
安全版更简单。如下:
#define MAX_ERROR_LEN 256
static __thread char buf[MAX_ERROR_LEN];
void my_strerror(int err)
{
if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
} else {
strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
}
return buf;
}
关于技术交流
此处后的文字已经和题目内容无关,可以不看。
qq群:825695030
微信公众号:嵌入式的日常
如果上面的文章对你有用,欢迎打赏、点赞、评论。