线程安全与可重入函数
线程安全:一个函数被称为线程安全的(thread-safe),当且仅当被多个并发进程反复调用时,它会一直产生正确的结果。如果一个函数不是线程安全的,我们就说它是线程不安全的(thread-unsafe)。我们定义四类(有相交的)线程不安全函数。
第1类:不保护共享变量的函数
将这类线程不安全函数变为线程安全的,相对比较容易:利用像P和V操作这样的同步操作来保护共享变量。这个方法的优点是在调用程序中不需要做任何修改,缺点是同步操作将减慢程序的执行时间。
第2类:保持跨越多个调用的状态函数
一个伪随机数生成器是这类不安全函数的简单例子。
unsigned int next = 1;
int rand(void)
{next = next * 1103515245 + 12345;return (unsigned int) (next / 65536) % 32768;
}void srand(unsigned int seed)
{next = seed;
}
rand函数是线程不安全的,因为当前调用的结果依赖于前次调用的中间结果。当我们调用srand为rand设置了一个种子后,我们反复从一个单线程中调用rand,我们能够预期一个可重复的随机数字序列。但是,如果有多个线程同时调用rand函数,这样的假设就不成立了。
使得rand函数变为线程安全的唯一方式是重写它,使得它不再使用任何静态数据,取而代之地依靠调用者在参数中传递状态信息。这样的缺点是,程序员现在要被迫改变调用程序的代码。
第3类:返回指向静态变量指针的函数
某些函数(如gethostbyname)将计算结果放在静态结构中,并返回一个指向这个结构的指针。如果我们从并发线程中调用这些函数,那么将可能发生灾难,因为正在被一个线程使用的结果会被另一个线程悄悄地覆盖了。
有两种方法来处理这类线程不安全函数。一种是选择重写函数,使得调用者传递存放结果的结构地址。这就消除了所有共享数据,但是它要求程序员还要改写调用者的代码。
如果线程不安全函数是难以修改或不可修改的(例如,它是从一个库中链接过来的),那么另外一种选择就是使用lock-and-copy(加锁-拷贝)技术。这个概念将线程不安全函数与互斥锁联系起来。在每个调用位置,对互斥锁加锁,调用函数不安全函数,动态地为结果非配存储器,拷贝函数返回的结果到这个存储器位置,然后对互斥锁解锁。一个吸引人的变化是定义了一个线程安全的封装(wrapper)函数,它执行lock-and-copy,然后调用这个封转函数来取代所有线程不安全的函数。例如下面的gethostbyname的线程安全函数。
struct hostent* gethostbyname_ts(char* host)
{struct hostent* shared, * unsharedp;unsharedp = Malloc(sizeof(struct hostent));P(&mutex)shared = gethostbyname(hostname);*unsharedp = * shared;V(&mutex);return unsharedp;
}
第4类:调用线程不安全函数的函数
如果函数f调用线程不安全函数g,那么f就是线程不安全的吗?不一定。如果g是类2类函数,即依赖于跨越多次调用的状态,那么f也是不安全的,而且除了重写g以外,没有什么办法。然而如果g是第1类或者第3类函数,那么只要用互斥锁保护调用位置和任何得到的共享数据,f可能仍然是线程安全的。比如上面的gethostbyname_ts。
可重入函数
可重入函数:可重入函数是线程安全函数的一种,其特点在于它们被多个线程调用时,不会引用任何共享数据。
可重入函数通常要比不可重入的线程安全函数效率高一些,因为它们不需要同步操作。更进一步说,将第2类线程不安全函数转化为线程安全函数的唯一方法就是重写它,使之可重入。
下面为rand函数的一个可重入版本
int rand_r(unsigned int* nextp)
{*nextp = *nextp * 1103515245 + 12345;return (unsigned int) (*nextp / 65536) % 32768;
}
显式可重入函数:如果所有函数的参数都是传值传递的(没有指针),并且所有的数据引用都是本地的自动栈变量(也就是说没有引用静态或全局变量),那么函数就是显示可重入的,也就是说不管如何调用,我们都可断言它是可重入的。
隐式可重入函数:可重入函数中的一些参数是引用传递(使用了指针),也就是说,在调用线程小心地传递指向非共享数据的指针时,它才是可重入的。例如rand_r就是隐式可重入的。
我们使用可重入(reentrant)来包括显式可重入函数和隐式可重入函数。然而,可重入性有时是调用者和被调用者共有的属性,并不只是被调用者单独的属性。
c语言非线程安全函数引发的BUG一列
公司的机器最近遇到一个bug(其实这个bug一年前就出现过,只是未引起重视),现象是这样的:在生产环境中,用户连续打印票的时候,中间某张票的一个时间可能会出问题,该时间本来是一个未来的时间,却被打印成了系统当前的时间.其他同事认为是传入的参数出错了,加了大量的调试信息和日志进去,结果去令人掉眼镜,传入的参数是完全正确的,但是结果去不是预期的.
最终排查BUG的任务转到了我手里,我简单分析了下上层流程,并无问题,于是把问题定位在底层库的FormatTime函数上,于是要了份FormatTime的代码查看(吐槽下,封闭的代码库往往造就一些隐蔽的问题),FormatTime的实现很简单,调用localtime函数,然后格式化输出,查看localtime函数的原型,如下
|
问题就出在localtime这个函数上,从函数原型来看,这个函数返回了一个struct tm的指针,但是传入参数并未有传入tm参数,那么就有3种情况
1,tm是内部malloc出来的
2,tm是一个全局变量
3,tm是一个局部static
第一种情可以首先排除,因为调用完localtime后没要求free tm,或者提供相应的free函数,第2 3种情况可以视为一种情况,那即是:使用了内部全局变量!
那么问题就来了,这个函数是个非线程安全的函数,在多线程的环境下使用,会发生重入的情况,即如果两个线程同时调用localtime函数,函数的返回结果都会是最后一次调用localtime的结果.这是一个典型的函数重入BUG.解决方法:使用线程安全的localtime_r函数替换localtime.至此,问题解决
c语言中有不少函数都是非线程安全的,例如strtok,gethostbyname,看到返回值为指针类型的函数时,都要留个心眼.
线程安全与可重入函数相关推荐
- 线程安全与可重入函数的区别及联系
一.线程安全 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 或者 ...
- [Linux]线程安全和可重入函数
线程安全:一个函数被称为线程安全的,当且仅当被多个并发进程反复调用时,它会一直产生正确的结果.如果一个函数不是线程安全的,我们就说它是线程不安全的. 重入:函数被不同的控制流程调用,有可能在第一次调用 ...
- 线程安全和可重入函数的联系与区别
1. 线程安全: 线程安全是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程访问完,其他线程才可以使用.不会出现数据不一致或数据污染. 线程 ...
- 线程安全和可重入函数
先说结论 可重入函数未必是线程安全的:线程安全函数未必是可重入的. 可重入函数的概念 可重入的程序(函数)允许在执行的过程中被打断,并在打断所执行的代码中再次安全的调用.重点在于安全,不允许程序挂掉. ...
- 线程安全与可重入函数的区别与联系
线程安全 线程安全是多个线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取结束并且释放了锁,其他线程才可使用,保证了数据的一致性. 与之对应的则是 ...
- 14.线程安全?线程不安全?可重入函数?不可重入函数?
线程安全问题 基本定义 线程安全:简单来说线程安全就是多个线程并发执行同一段代码时,不会出现不同的结果,我们就可以说该线程是安全的: 线程不安全:如果多线程并发执行时会产生不同的结果,则该线程就是不安 ...
- 可重入函数 与线程安全的区别与联系
线程安全:多个线程访问同一个区域的时候其最终结果是可预期的,并不会因为产生冲突或者异常中断再次恢复而使结果不可预期 1.重入:函数被不同的控制流程调用,有可能在第一次调用还没有返回的时候就再次进入该函 ...
- 可重入函数与线程安全的区别与联系
本文主要介绍一下可重入函数与线程安全的区别与联系,在此之前我们先来了解一些基本概念:什么是线程全函数,什么是可重入函数? 线程安全函数 概念 线程安全的概念比较直观,一般来说,一个函数被称为线程安全的 ...
- Linux中的可重入函数和不可重入函数
可重入函数 可重入函数(即可以被中断的函数)可以被一个以上的任务调用,而不担心数据破坏.可重入函数在任何时候都可以被中断,而一段时间之后又可以恢复运行,而相应的数据不会破坏或者丢失. 可重入函数使用的 ...
最新文章
- 如何高效地爬取链家的房源信息(二)
- xutils使用手册(二)——数据库
- 清理Visual Studio2010产生的垃圾调试文件
- O2O产品经理,请多关注屏幕之外
- linux怎样用命令提示符,Linux用户必知:一分钟掌握14个常用Linux命令行快捷键
- 【翻译】.NET 5 RC1发布
- Redis进阶实践之二十 Redis的配置文件使用详解
- Java基础篇4——数组
- CSS中z-index
- linux自建git仓库
- python---Matplotlib简单用法
- wget 网页爬虫,网页抓取工具
- 刷新计算机dns缓存的命令,电脑如何刷新dns缓存【图解】
- Systrace 解析
- 金沙滩51单片机LED 闪烁程序
- 数据库中反引号的作用
- 1020寒星孤月-蛇形矩阵
- 说一下国内做MES的几类厂商
- MPLS LDP的原理与配置
- 并行程序设计方法实验(包括openmp、向量化实现pi计算、SPECOMP2012测试、矩阵乘法优化)
热门文章
- Git 系列(二):初步了解 Git
- ComboBox控件-转
- Python之调用JS的方式
- 【数据结构与算法】之深入解析“删除链表的倒数第N个结点”的求解思路与算法示例
- OpenGL ES之GLSL实现仿抖音“缩放”“灵魂出窍”“抖动”“闪白”“毛刺”“幻觉”等动态滤镜效果
- LeetCode Algorithm 160. 相交链表
- 打击诈骗,逻辑回归闪亮登场
- 2018年第九届蓝桥杯 - 国赛 - C/C++大学B组 - B. 激光样式
- 深度学习——02、深度学习入门——经典卷积神经网络架构实例——RNN
- 【Qt】QSharedMemory类详解