线程安全:一个函数被称为线程安全的(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函数的原型,如下

struct tm *localtime(const time_t *timep);

  问题就出在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,看到返回值为指针类型的函数时,都要留个心眼.

线程安全与可重入函数相关推荐

  1. 线程安全与可重入函数的区别及联系

    一.线程安全 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的.  或者 ...

  2. [Linux]线程安全和可重入函数

    线程安全:一个函数被称为线程安全的,当且仅当被多个并发进程反复调用时,它会一直产生正确的结果.如果一个函数不是线程安全的,我们就说它是线程不安全的. 重入:函数被不同的控制流程调用,有可能在第一次调用 ...

  3. 线程安全和可重入函数的联系与区别

    1.    线程安全: 线程安全是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程访问完,其他线程才可以使用.不会出现数据不一致或数据污染. 线程 ...

  4. 线程安全和可重入函数

    先说结论 可重入函数未必是线程安全的:线程安全函数未必是可重入的. 可重入函数的概念 可重入的程序(函数)允许在执行的过程中被打断,并在打断所执行的代码中再次安全的调用.重点在于安全,不允许程序挂掉. ...

  5. 线程安全与可重入函数的区别与联系

    线程安全 线程安全是多个线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取结束并且释放了锁,其他线程才可使用,保证了数据的一致性. 与之对应的则是 ...

  6. 14.线程安全?线程不安全?可重入函数?不可重入函数?

    线程安全问题 基本定义 线程安全:简单来说线程安全就是多个线程并发执行同一段代码时,不会出现不同的结果,我们就可以说该线程是安全的: 线程不安全:如果多线程并发执行时会产生不同的结果,则该线程就是不安 ...

  7. 可重入函数 与线程安全的区别与联系

    线程安全:多个线程访问同一个区域的时候其最终结果是可预期的,并不会因为产生冲突或者异常中断再次恢复而使结果不可预期 1.重入:函数被不同的控制流程调用,有可能在第一次调用还没有返回的时候就再次进入该函 ...

  8. 可重入函数与线程安全的区别与联系

    本文主要介绍一下可重入函数与线程安全的区别与联系,在此之前我们先来了解一些基本概念:什么是线程全函数,什么是可重入函数? 线程安全函数 概念 线程安全的概念比较直观,一般来说,一个函数被称为线程安全的 ...

  9. Linux中的可重入函数和不可重入函数

    可重入函数 可重入函数(即可以被中断的函数)可以被一个以上的任务调用,而不担心数据破坏.可重入函数在任何时候都可以被中断,而一段时间之后又可以恢复运行,而相应的数据不会破坏或者丢失. 可重入函数使用的 ...

最新文章

  1. 如何高效地爬取链家的房源信息(二)
  2. xutils使用手册(二)——数据库
  3. 清理Visual Studio2010产生的垃圾调试文件
  4. O2O产品经理,请多关注屏幕之外
  5. linux怎样用命令提示符,Linux用户必知:一分钟掌握14个常用Linux命令行快捷键
  6. 【翻译】.NET 5 RC1发布
  7. Redis进阶实践之二十 Redis的配置文件使用详解
  8. Java基础篇4——数组
  9. CSS中z-index
  10. linux自建git仓库
  11. python---Matplotlib简单用法
  12. wget 网页爬虫,网页抓取工具
  13. 刷新计算机dns缓存的命令,电脑如何刷新dns缓存【图解】
  14. Systrace 解析
  15. 金沙滩51单片机LED 闪烁程序
  16. 数据库中反引号的作用
  17. 1020寒星孤月-蛇形矩阵
  18. 说一下国内做MES的几类厂商
  19. MPLS LDP的原理与配置
  20. 并行程序设计方法实验(包括openmp、向量化实现pi计算、SPECOMP2012测试、矩阵乘法优化)

热门文章

  1. Git 系列(二):初步了解 Git
  2. ComboBox控件-转
  3. Python之调用JS的方式
  4. 【数据结构与算法】之深入解析“删除链表的倒数第N个结点”的求解思路与算法示例
  5. OpenGL ES之GLSL实现仿抖音“缩放”“灵魂出窍”“抖动”“闪白”“毛刺”“幻觉”等动态滤镜效果
  6. LeetCode Algorithm 160. 相交链表
  7. 打击诈骗,逻辑回归闪亮登场
  8. 2018年第九届蓝桥杯 - 国赛 - C/C++大学B组 - B. 激光样式
  9. 深度学习——02、深度学习入门——经典卷积神经网络架构实例——RNN
  10. 【Qt】QSharedMemory类详解