最近阅读一份linux的线程代码时,看到了一套函数,pthread_getspecific/pthread_setspecific。光从名字上,完全无法理解出他们到底是干啥的,结合代码来看,也不是很清楚。于是就去百度。可是,百度来百度去,CSDN来CSDN去,反反复复找了不少资料,却始终没能完全理解透彻。感觉大家都是从同一份博客里抄来抄去的,甚至连其中的错误都照抄了,不但没能解答我的疑惑,反而还带来了其他的问题。

题外话一:这里实在想吐槽一下百度的学术资料搜索能力,跟人家google比,实在是差太多了,找半天都找不到什么有用的资料。广告搜索能力倒是挺强,各种推广广告一堆堆的跳出来,而且是在最显眼的地方。为什么不能把广告的搜索能力移植到学术资料的搜索上来呢?

题外话二:这里还想吐槽一下部分CSDN的网友,看到别人的博客,自己也不验证验证,消化消化,就直接复制黏贴过来,关键也不注明一下是不是转载的,更不用说贴上源地址了。我找资料时,一个关键词输进去,链接确实能跳出来不少,但是点进去发现TM全是同一篇文章,只是由不同的博主转来转去的,绝大部分人甚至连原博文的错误都一并转载了,实在是哭笑不得。

不管如何,自己的疑问还是要搞清楚的。于是继续查阅资料,并对这两个函数的用法进行总结和实践,总算是基本搞清了。下面对我自己的心得进行一下总结,希望能给其他小伙伴提供一下借鉴。

一、技术原理

首先要交代一下,pthread_getspecific/pthread_setspecific这两个函数,其实是属于线程数据存储机制【也叫线程私有数据,英文名Thread Specific Data】的一部分。因此,要先引入线程存储机制这个大话题,才能进行比较全面而清晰的了解。

大家都知道,在多线程程序中,一个模块里的全局变量,对于该模块的所有线程都是可见的,也就是说全局共享的,所有线程都可以使用它,可以随时改变它的值,这显然是不安全的,会带来一些风险。那么有没有什么办法能改善这个问题呢?在强大的linux系统下,一切皆有可能!这就到了线程存储机制隆重登场的时候了!应用了线程存储机制之后,这个变量表面上看起来还是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独隔离的,彼此之间不会互相影响。A线程对该变量的值进行修改之后,相应的变化只会在A线程中体现,其他的线程中去查询该变量时,得到的依然是本线程中的值,跟A线程改动的值无关。

比如linux系统下常见的errno,它返回标准的错误码。errno肯定不是一个局部变量,因为几乎每个函数都可以访问他。但它又不能是一个简单的全局变量,否则在一个线程里输出的很可能是另一个线程的出错信息。因此,必须借助于创建线程的私有数据来解决。线程私有数据采用一键多值的技术,即一个键对应多个值。访问数据时都是通过键值来访问,表面上看上去好像是对同一个变量进行访问,但是实际上访问的是不同的数据空间。

线程存储机制的具体用法如下:

  1. 首先要定义一个类型为pthread_key_t类型的全局变量。
  2. 调用pthread_key_create()来创建该变量。该函数有两个参数,第一个参数就是上面声明的pthread_key_t变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成NULL,这样系统将调用默认的清理函数。该函数调用成功时返回0,返回其他任何值都表示出现了错误。
  3. 当线程中需要存储特殊值的时候,可以调用pthread_setspcific()。该函数有两个参数,第一个为前面声明的pthread_key_t变量,第二个为void*变量,这样你可以存储任何类型的值。
  4. 如果需要取出所存储的值,调用pthread_getspecific()。该函数的参数为前面提到的pthread_key_t变量,该函数返回void *类型的值。
  5. 注销使用pthread_key_delete()函数,该函数并不检查当前是否有线程正在使用,也不会调用清理函数,而只是释放以供下一次调用pthread_key_create()使用。

下面是前面提到的函数原型:

#include <pthread.h>int pthread_key_create(pthread_key_t *key, void (*destructor)(void *));
int pthread_key_delete(pthread_key_t *key);
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);

二、实例解析

下面的代码是从网上摘录过来,内容很简单,就是用来验证这两个函数的使用。

新建一个模块命名为test.c,输入以下内容:

#include <stdio.h>
#include <pthread.h>
#include <string.h>static pthread_key_t p_key;void *thread_func(void *args)
{int *tmp1, *tmp2;/* 注意:这里初始化私有键值时使用的特征值,是该线程创建时传入的特征值a/b */pthread_setspecific(p_key, args);                                          /* 初始化本线程的私有键值 */printf("in thread %d. init specific_data to %d\n", *(int *)args, *(int *)args);tmp1 = (int *)pthread_getspecific(p_key);                                  /* 获取私有键值的内容 */printf("get specific_data %d\n", *tmp1);*tmp1 = (*tmp1) * 100;                                                     /* 修改私有键值的内容 */printf("change specific_data to %d\n", *tmp1);tmp2 = (int *)pthread_getspecific(p_key);                                  /* 重新获取本线程的私有键值 */printf("get specific_data %d\n", *tmp2);return (void *)0;
}int main()
{int a = 1;int b = 2;pthread_t pa, pb;printf("at first: a = %d. b = %d\n", a, b);pthread_key_create(&p_key, NULL);                                          /* 首先创建私有数据键值 */pthread_create(&pa, NULL, thread_func, &a);                                /* 创建线程1 */pthread_create(&pb, NULL, thread_func, &b);                                /* 创建线程2 */pthread_join(pa, NULL);pthread_join(pb, NULL);/* 解释下pthread_join函数:它的目的是使一个线程等待另一个线程结束 *//* 代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,创建的线程根本来不及执行 *//* 加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行 */printf("at last: a = %d. b = %d\n", a, b);return 0;
}

编译链接并运行,可以看到输出信息如下:

leon@Ubuntu:~/test$ gcc test.c -lpthread -o test
leon@Ubuntu:~/test$ ./test
at first: a = 1. b = 2
in thread 1. init specific_data to 1
get specific_data 1
change specific_data to 100
get specific_data 100
in thread 2. init specific_data to 2
get specific_data 2
change specific_data to 200
get specific_data 200
at last: a = 100. b = 200

可以看出,两个线程中分别对私有键值进行了初始化、读取、修改、读取的过程,所得到的键值按照预期的规律发生了变化,同时又彼此独立互不影响。

三、补充说明

这里要说明一下,不少CSDN网友所转载的文章里,都对上面的代码使用“gcc -lpthread test.c -o test”这样的语句进行编译链接,但是却没人发现这里面的问题!!

大家可以自己去编译一下试试看,如果你输入“gcc -lpthread test.c -o test”,绝对是编译不过的,系统会提示:

/tmp/ccSkayWg.o:在函数‘test’中:
thread_spc.c:(.text+0x11):对‘pthread_getspecific’未定义的引用
/tmp/ccSkayWg.o:在函数‘thread_func’中:
thread_spc.c:(.text+0x54):对‘pthread_setspecific’未定义的引用
thread_spc.c:(.text+0x61):对‘pthread_getspecific’未定义的引用
/tmp/ccSkayWg.o:在函数‘main’中:
thread_spc.c:(.text+0xd0):对‘pthread_key_create’未定义的引用
thread_spc.c:(.text+0xed):对‘pthread_create’未定义的引用
thread_spc.c:(.text+0x10a):对‘pthread_create’未定义的引用
thread_spc.c:(.text+0x11b):对‘pthread_join’未定义的引用
thread_spc.c:(.text+0x12c):对‘pthread_join’未定义的引用

这是因为gcc依赖顺序的问题导致的。就是gcc编译的时候,各个文件的依赖顺序是有讲究的。如果文件a依赖于文件b,那么编译的时候必须把a放前面,b放后面。

例如,在main.c中使用了pthread库相关函数,那么编译的时候必须是main.c在前,-lpthread在后:

gcc main.c -lpthread -o a.out

所以,上面出现问题的原因就是引入库的顺序出了问题,不能放在前面,得放到后面去:

gcc test.c -lpthread -o test

线程安全存储以及pthread_getspecific/pthread_setspecific相关推荐

  1. java线程本地存储_[并发并行]_[C/C++]_[使用线程本地存储Thread Local Storage(TLS)-win32和pthread比较]...

    场景: 1.  需要统计某个线程的对象上创建的个数. 2. 当创建的堆空间需要根据线程需要创建和结束时销毁时. 3. 因为范围是线程只能看到自己的存储数据,所以不需要临界区或互斥量来维护自己的堆内存. ...

  2. c语言 多个线程对同一变量执行memcpy_你可曾听过网络编程中应用线程本地存储?...

    壹:你可曾听过线程本地存储? 1. 什么是线程本地存储? 线程本地存储:thread local storage(简称TLS).也叫线程特有存储:thread specific storage(简称T ...

  3. pthread_getspecific pthread_setspecific

    #include <pthread.h> void *pthread_getspecific(pthread_key_t key); int pthread_setspecific(pth ...

  4. 线程安全的map_ThreadLocal | 线程本地存储

    并发场景下,多个线程同时读写共享变量就有可能产生并发安全问题.反过来也可以说,不存在共享变量,就不会出现线程安全问题.Java中有两种常用的避免共享变量的方法,使用局部变量,以及使用 ThreadLo ...

  5. Java中的线程本地存储

    开发人员中鲜为人知的功能之一是线程本地存储. 这个想法很简单,并且在需要数据的情况下很有用. 如果我们有两个线程,则它们引用相同的全局变量,但我们希望它们具有彼此独立初始化的单独值. 大多数主要的编程 ...

  6. linux 有线程本地存储 (tls)?,有没有办法确定Linux上的库使用的线程本地存储模型...

    我自己遇到了这个错误,在调查时,我来了一个 mailing list post with this info: If you link a shared object containing IE-mo ...

  7. 聊聊Linux中的线程本地存储(1)——什么是TLS

    从本篇开始进入另一个话题:线程本地存储(Thread Local Storage),在介绍这个概念前先说说变量和多线程的相关知识. 多线程下的变量模型 在单线程模型下,变量定义有两个维度,那就是在何处 ...

  8. 现代c++之线程本地存储thread_local

    线程1, 打印x=1, y=1 线程2, 打印x=2, y=1 主线程, 打印x=2, y=0 y每个线程都有一个实例 . x是线程共享的.

  9. ThreadLocal线程本地存储

    2019独角兽企业重金招聘Python工程师标准>>> 什么是ThreadLocal?首先要说明的一点是ThreadLocal并不是一个Thread,而是Thread的局部变量.在J ...

最新文章

  1. 趋势科技全球首席安全官ED:人类迈向智能社会进程中不能失去掌控力
  2. 机智云5.0推出IoT套件GoKit4.0 可实现物联网应用协同开发
  3. 把自己的文件做成ISO镜像文件
  4. 使用spi协议,接收来自slave的数据之前写0xff的原因
  5. python扫盲系列-(2)
  6. 登录:应用程序错误通知
  7. 计算机等级考试中的c语言程序
  8. Windos环境用Nginx配置反向代理和负载均衡
  9. How to stop looking for someone perfect and find someone to love
  10. js里写网页结构, 传函数参数
  11. RADIUS服务器的演变过程
  12. 【个人经验】3个步骤教你拿到软件著作权证书
  13. 到底谁害了快播?或是服务器标准
  14. (对对碰)软工结对作业
  15. 第27月第25天 clang -rewrite-objc main.m
  16. 微信公众平台中怎么上传附件?
  17. 支付宝指纹服务器暂时用不了,解决支付宝指纹验证失效的问题
  18. 云渲染解决方案,CPU渲染,GPU渲染
  19. 秒辞的95后vs不敢辞的中年人,数据分析告诉你员工离职的真正原因!
  20. zte me3xxx 4g调试

热门文章

  1. count 和列不能一起查am_AM-RB 003 会不会让 Aston Martin 变成下一个法拉利?
  2. 编译maven_头条一面竟然问我Maven?
  3. linux c 获取网关ip,linux sh 如何根据出口网关来获取本机出口ip
  4. Vue实现的滑动切换路由组件
  5. 移动医疗:医护到家或成关键
  6. Redis的几个认识误区
  7. work hard, think harder
  8. aix和linux脚本,AIX系统资源监控SHELL脚本
  9. rtsp协议_Chromium(3/5):rtsp客户端
  10. mysql 单表字段多少合适_复制信息记录表|全方位认识 mysql 系统库