线程局部存储的三种方式

在某些情况下,我们需要使用线程独有的变量,其他线程不干扰。实现的方式主要有以下三种,一是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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值