在某些情况下,我们需要使用线程独有的变量,其他线程不干扰。实现的方式主要有以下三种,一是gcc提供的_thread, 二是pthread提供的pthread_key_t ,三则是c++11提供的thread_local。无论那种方式,它们都能保证变量只对当前线程可见。
1. gcc中的_thread
_thread 修饰线程周期的变量,主要用法有
__thread int i;
extern __thread struct state s;
static __thread char *p;
它需要链接器(ld),动态链接器(ld.so)和系统库(libc.so和libpthread.so)的支持,也就是说在gcc之外它可能不能用。
它有以下特点
- 只能修饰POD类型(int、long、float、double等),不可用于class类型
- 可以用于修饰全局变量,函数内的静态变量,不能修饰函数的局部变量或者class的普通成员变量,编译时会有warning提示
- __thread变量值只能用常量或者const变量初始化,不能用变量初始化
示例
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
// int i = 6;//error , 不能用非const 去给_thread变量赋值
const int i = 5;
__thread int var1 = i; // 1. __thread修饰普通的全局变量,可以被其他文件extern
static __thread int var2 = 15; // 2. 修饰static全局变量
static void* func1(void* arg) {
printf("%s var1 addr :%p\n", __FUNCTION__, &var1);
printf("%s var2 addr :%p\n", __FUNCTION__, &var2);
}
static void* func2(void* arg) {
printf("%s var1 addr :%p\n", __FUNCTION__, &var1);
printf("%s var2 addr :%p\n", __FUNCTION__, &var2);
}
int main() {
pthread_t pid1, pid2;
static __thread int number = 512; // 3. 修饰函数内的static变量
pthread_create(&pid1, NULL, func1, NULL);
pthread_create(&pid2, NULL, func2, NULL);
pthread_join(pid1, NULL);
pthread_join(pid2, NULL);
printf("%s var1 addr :%p\n", __FUNCTION__, &var1);
printf("%s var2 addr :%p\n", __FUNCTION__, &var2);
printf("%s number :%d\n", __FUNCTION__, number);
return 0;
}
测试
可见不同线程中的变量,地址是不同的。说明每一个线程中都有一份,它们之间互不影响。
[root@localhost others]# g++ _thread.cpp -lpthread -o _thread
[root@localhost others]# ./_thread
func1 var1 addr :0x7f53cd2c86f4
func1 var2 addr :0x7f53cd2c86f8
func2 var1 addr :0x7f53ccac76f4
func2 var2 addr :0x7f53ccac76f8
main var1 addr :0x7f53ce2b2734
main var2 addr :0x7f53ce2b2738
main number :512
2. pthread_key_t
pthread_key_t 总体上要比_thread好用些,使用更广泛。一个是它依赖libpthread库,而不是gcc。第二点,它可以通过指针指向class类型。并且它还可以作为局部变量。
它的实现原理是一键多值技术,key被创建之后,所有线程都可以访问它,各个线程可以往key中写入不同的值。各个线程通过key来访问数据的时候,访问的是不同的数据,实现此功能有一个关键的数据结构TSD(Thread- specific Date)池,相当于一个数组,对应有各个线程的私有值。如下
操作线程私有数据的主要通过以下4个函数来实现:
- pthread_key_create(创建key)
- pthread_setspecific(赋值)
- pthread_getspecific(取值)
- pthread_key_delete(删除)
示例
线程1 线程2 分别通过key指向一个自己申请的动态内存,销毁key时,这两块内存分别释放
// gcc demo.c -o demo -lpthread
#include <pthread.h>
#include <stdio.h>
#include <string.h>
pthread_key_t key;
void *thread2(void *arg) {
char *s = (char *)calloc(1, 10);
strcpy(s, "thread2-s");
pthread_setspecific(key, (void *)s);
printf("%s : %s\n", __FUNCTION__, pthread_getspecific(key));
}
void *thread1(void *arg) {
char *s = (char *)calloc(1, 10);
strcpy(s, "thread1-s");
pthread_setspecific(key, (void *)s);
printf("%s : %s\n", __FUNCTION__, pthread_getspecific(key));
}
// 删除器, 会调用两次
void destr(void *arg) {
if (arg) {
printf("free %s\n", (char *)arg);
free(arg);
}
}
int main(void) {
pthread_t thid1;
pthread_t thid2;
pthread_key_create(&key, destr); // 创建一个key
pthread_create(&thid1, NULL, thread1, NULL);
pthread_create(&thid2, NULL, thread2, NULL);
sleep(4);
pthread_key_delete(key); // 销毁
return 0;
}
3. C++11 thread_local的使用方法
见 https://blog.csdn.net/niu91/article/details/109103640 ,这篇博客中已经有总结
参考
[0] https://gcc.gnu.org/onlinedocs/gcc-3.4.1/gcc/Thread-Local.html
[1] https://blog.csdn.net/rain_qingtian/article/details/11492365
[2] https://blog.csdn.net/niu91/article/details/109103640
[3] https://linux.die.net/man/3/pthread_key_create