线程安全存储以及pthread_getspecific/pthread_setspecific
最近阅读一份linux的线程代码时,看到了一套函数,pthread_getspecific/pthread_setspecific。光从名字上,完全无法理解出他们到底是干啥的,结合代码来看,也不是很清楚。于是就去百度。可是,百度来百度去,CSDN来CSDN去,反反复复找了不少资料,却始终没能完全理解透彻。感觉大家都是从同一份博客里抄来抄去的,甚至连其中的错误都照抄了,不但没能解答我的疑惑,反而还带来了其他的问题。
题外话一:这里实在想吐槽一下百度的学术资料搜索能力,跟人家google比,实在是差太多了,找半天都找不到什么有用的资料。广告搜索能力倒是挺强,各种推广广告一堆堆的跳出来,而且是在最显眼的地方。为什么不能把广告的搜索能力移植到学术资料的搜索上来呢?
题外话二:这里还想吐槽一下部分CSDN的网友,看到别人的博客,自己也不验证验证,消化消化,就直接复制黏贴过来,关键也不注明一下是不是转载的,更不用说贴上源地址了。我找资料时,一个关键词输进去,链接确实能跳出来不少,但是点进去发现TM全是同一篇文章,只是由不同的博主转来转去的,绝大部分人甚至连原博文的错误都一并转载了,实在是哭笑不得。
不管如何,自己的疑问还是要搞清楚的。于是继续查阅资料,并对这两个函数的用法进行总结和实践,总算是基本搞清了。下面对我自己的心得进行一下总结,希望能给其他小伙伴提供一下借鉴。
一、技术原理
首先要交代一下,pthread_getspecific/pthread_setspecific这两个函数,其实是属于线程数据存储机制【也叫线程私有数据,英文名Thread Specific Data】的一部分。因此,要先引入线程存储机制这个大话题,才能进行比较全面而清晰的了解。
大家都知道,在多线程程序中,一个模块里的全局变量,对于该模块的所有线程都是可见的,也就是说全局共享的,所有线程都可以使用它,可以随时改变它的值,这显然是不安全的,会带来一些风险。那么有没有什么办法能改善这个问题呢?在强大的linux系统下,一切皆有可能!这就到了线程存储机制隆重登场的时候了!应用了线程存储机制之后,这个变量表面上看起来还是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独隔离的,彼此之间不会互相影响。A线程对该变量的值进行修改之后,相应的变化只会在A线程中体现,其他的线程中去查询该变量时,得到的依然是本线程中的值,跟A线程改动的值无关。
比如linux系统下常见的errno,它返回标准的错误码。errno肯定不是一个局部变量,因为几乎每个函数都可以访问他。但它又不能是一个简单的全局变量,否则在一个线程里输出的很可能是另一个线程的出错信息。因此,必须借助于创建线程的私有数据来解决。线程私有数据采用一键多值的技术,即一个键对应多个值。访问数据时都是通过键值来访问,表面上看上去好像是对同一个变量进行访问,但是实际上访问的是不同的数据空间。
线程存储机制的具体用法如下:
- 首先要定义一个类型为pthread_key_t类型的全局变量。
- 调用pthread_key_create()来创建该变量。该函数有两个参数,第一个参数就是上面声明的pthread_key_t变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成NULL,这样系统将调用默认的清理函数。该函数调用成功时返回0,返回其他任何值都表示出现了错误。
- 当线程中需要存储特殊值的时候,可以调用pthread_setspcific()。该函数有两个参数,第一个为前面声明的pthread_key_t变量,第二个为void*变量,这样你可以存储任何类型的值。
- 如果需要取出所存储的值,调用pthread_getspecific()。该函数的参数为前面提到的pthread_key_t变量,该函数返回void *类型的值。
- 注销使用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相关推荐
- java线程本地存储_[并发并行]_[C/C++]_[使用线程本地存储Thread Local Storage(TLS)-win32和pthread比较]...
场景: 1. 需要统计某个线程的对象上创建的个数. 2. 当创建的堆空间需要根据线程需要创建和结束时销毁时. 3. 因为范围是线程只能看到自己的存储数据,所以不需要临界区或互斥量来维护自己的堆内存. ...
- c语言 多个线程对同一变量执行memcpy_你可曾听过网络编程中应用线程本地存储?...
壹:你可曾听过线程本地存储? 1. 什么是线程本地存储? 线程本地存储:thread local storage(简称TLS).也叫线程特有存储:thread specific storage(简称T ...
- pthread_getspecific pthread_setspecific
#include <pthread.h> void *pthread_getspecific(pthread_key_t key); int pthread_setspecific(pth ...
- 线程安全的map_ThreadLocal | 线程本地存储
并发场景下,多个线程同时读写共享变量就有可能产生并发安全问题.反过来也可以说,不存在共享变量,就不会出现线程安全问题.Java中有两种常用的避免共享变量的方法,使用局部变量,以及使用 ThreadLo ...
- Java中的线程本地存储
开发人员中鲜为人知的功能之一是线程本地存储. 这个想法很简单,并且在需要数据的情况下很有用. 如果我们有两个线程,则它们引用相同的全局变量,但我们希望它们具有彼此独立初始化的单独值. 大多数主要的编程 ...
- linux 有线程本地存储 (tls)?,有没有办法确定Linux上的库使用的线程本地存储模型...
我自己遇到了这个错误,在调查时,我来了一个 mailing list post with this info: If you link a shared object containing IE-mo ...
- 聊聊Linux中的线程本地存储(1)——什么是TLS
从本篇开始进入另一个话题:线程本地存储(Thread Local Storage),在介绍这个概念前先说说变量和多线程的相关知识. 多线程下的变量模型 在单线程模型下,变量定义有两个维度,那就是在何处 ...
- 现代c++之线程本地存储thread_local
线程1, 打印x=1, y=1 线程2, 打印x=2, y=1 主线程, 打印x=2, y=0 y每个线程都有一个实例 . x是线程共享的.
- ThreadLocal线程本地存储
2019独角兽企业重金招聘Python工程师标准>>> 什么是ThreadLocal?首先要说明的一点是ThreadLocal并不是一个Thread,而是Thread的局部变量.在J ...
最新文章
- 趋势科技全球首席安全官ED:人类迈向智能社会进程中不能失去掌控力
- 机智云5.0推出IoT套件GoKit4.0 可实现物联网应用协同开发
- 把自己的文件做成ISO镜像文件
- 使用spi协议,接收来自slave的数据之前写0xff的原因
- python扫盲系列-(2)
- 登录:应用程序错误通知
- 计算机等级考试中的c语言程序
- Windos环境用Nginx配置反向代理和负载均衡
- How to stop looking for someone perfect and find someone to love
- js里写网页结构, 传函数参数
- RADIUS服务器的演变过程
- 【个人经验】3个步骤教你拿到软件著作权证书
- 到底谁害了快播?或是服务器标准
- (对对碰)软工结对作业
- 第27月第25天 clang -rewrite-objc main.m
- 微信公众平台中怎么上传附件?
- 支付宝指纹服务器暂时用不了,解决支付宝指纹验证失效的问题
- 云渲染解决方案,CPU渲染,GPU渲染
- 秒辞的95后vs不敢辞的中年人,数据分析告诉你员工离职的真正原因!
- zte me3xxx 4g调试
热门文章
- count 和列不能一起查am_AM-RB 003 会不会让 Aston Martin 变成下一个法拉利?
- 编译maven_头条一面竟然问我Maven?
- linux c 获取网关ip,linux sh 如何根据出口网关来获取本机出口ip
- Vue实现的滑动切换路由组件
- 移动医疗:医护到家或成关键
- Redis的几个认识误区
- work hard, think harder
- aix和linux脚本,AIX系统资源监控SHELL脚本
- rtsp协议_Chromium(3/5):rtsp客户端
- mysql 单表字段多少合适_复制信息记录表|全方位认识 mysql 系统库