由于前段时间,程序偶尔异常挂起不工作,检查后发现时死锁了,原因就是:在信号处理函数里面调用了fprintf. printf等io函数是需要对输出缓冲区加锁,这类函数对本身是线程安全的,但是对信号处理函数来说是不可重入的(在没有返回之前,不能再次调用),即不是异步信号安全的。
 
对于printf这类函数,可以这样理解:它们使用了全局数据结构(iobuffer),所以不是线程安全的(多个线程同时访问共享资源),也是不可重入的(有共享资源,可能损坏);
 
通过加锁可以变得线程安全,但是仍然不可重入。
 
对于返回值共享的函数可以通过自己传入地址的方式变得线程可以重入(即线程安全)“_r”类函数。
 
如果一个函数返回一个静态地址(如上),同时又使用了全局资源(已经加锁保护),那么即使使用上了的_r方法变得线程可以重入,也不代表信号处理函数可以重入(即异步信号安全)。
 
网上找了下,下面这篇将的还行:
 
对于可重入、线程安全、异步信号安全几个概念的理解
可重入与异步信号安全

一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误。

《多线程编程指南》中定义,可以被信号控制器安全调用的函数被称为"异步信号安全"函数。

因此,我认为可重入与异步信号安全是一个概念。



有人将可重入函数与线程安全函数混为一谈,我认为是不正确的。



这里引用CSAPP中的描述来说明一下:

--------------------------------------------------

CSAPP

13.7.1 线程安全

一个函数被称为线程安全的,当且仅当被多个并发线程反复的调用时,它会一直产生正确的结果。

13.7.2 可重入性

有一类重要的线程安全函数,叫做可重入函数,其特点在于它们具有一种属性:当它们被多个线程调用时,不会引用任何共享的数据。



尽管线程安全和可重入有时会(不正确的)被用做同义词,但是它们之间还是有清晰的技术差别的。可重入函数是线程安全函数的一个真子集。

--------------------------------------------------



重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。

可重入函数是线程安全函数,但是反过来,线程安全函数未必是可重入函数。

实际上,可重入函数很少,APUE 10.6节中描述了Single UNIX Specification说明的可重入的函数,只有115个;APUE 12.5节中描述了POSIX.1中不能保证线程安全的函数,只有89个。



信号就像硬件中断一样,会打断正在执行的指令序列。信号处理函数无法判断捕获到信号的时候,进程在何处运行。如果信号处理函数中的操作与打断的函数的操作相同,而且这个操作中有静态数据结构等,当信号处理函数返回的时候(当然这里讨论的是信号处理函数可以返回),恢复原先的执行序列,可能会导致信号处理函数中的操作覆盖了之前正常操作中的数据。

不可重入函数的原因在于:

1> 已知它们使用静态数据结构

2> 它们调用malloc和free.

因为malloc通常会为所分配的存储区维护一个链接表,而插入执行信号处理函数的时候,进程可能正在修改此链接表。

3> 它们是标准IO函数.

因为标准IO库的很多实现都使用了全局数据结构



即使对于可重入函数,在信号处理函数中使用也需要注意一个问题就是errno。一个线程中只有一个errno变量,信号处理函数中使用的可重入函数也有可能会修改errno。例如,read函数是可重入的,但是它也有可能会修改errno。因此,正确的做法是在信号处理函数开始,先保存errno;在信号处理函数退出的时候,再恢复errno。



例如,程序正在调用printf输出,但是在调用printf时,出现了信号,对应的信号处理函数也有printf语句,就会导致两个printf的输出混杂在一起。

如果是给printf加锁的话,同样是上面的情况就会导致死锁。对于这种情况,采用的方法一般是在特定的区域屏蔽一定的信号。

屏蔽信号的方法:

1> signal(SIGPIPE, SIG_IGN); //忽略一些信号

2> sigprocmask()

sigprocmask只为单线程定义的

3> pthread_sigmask()

pthread_sigmasks可以在多线程中使用



现在看来信号异步安全和可重入的限制似乎是一样的,所以这里把它们等同看待;-)



线程安全

线程安全:如果一个函数在同一时刻可以被多个线程安全的调用,就称该函数是线程安全的。



不需要共享时,请为每个线程提供一个专用的数据副本。如果共享非常重要,则提供显式同步,以确保程序以确定的方式操作。通过将过程包含在语句中来锁定和解除锁定互斥,可以使不安全过程变成线程安全过程,而且可以进行串行化。



很多函数并不是线程安全的,因为他们返回的数据是存放在静态的内存缓冲区中的。通过修改接口,由调用者自行提供缓冲区就可以使这些函数变为线程安全的。

操作系统实现支持线程安全函数的时候,会对POSIX.1中的一些非线程安全的函数提供一些可替换的线程安全版本。

例如,gethostbyname()是线程不安全的,在Linux中提供了gethostbyname_r()的线程安全实现。

函数名字后面加上"_r",以表明这个版本是可重入的(对于线程可重入,也就是说是线程安全的,但并不是说对于信号处理函数也是可重入的,或者是异步信号安全的)。



多线程程序中常见的疏忽性问题

1> 将指针作为新线程的参数传递给调用方栈。

2> 在没有同步机制保护的情况下访问全局内存的共享可更改状态。

3> 两个线程尝试轮流获取对同一对全局资源的权限时导致死锁。其中一个线程控制第一种资源,另一个线程控制第二种资源。其中一个线程放弃之前,任何一个线程都无法继续

操作。

4> 尝试重新获取已持有的锁(递归死锁)。

5> 在同步保护中创建隐藏的间隔。如果受保护的代码段包含的函数释放了同步机制,而又在返回调用方之前重新获取了该同步机制,则将在保护中出现此间隔。结果具有误导性。对于调用方,表面上看全局数据已受到保护,而实际上未受到保护。

6> 将UNIX 信号与线程混合时,使用sigwait(2) 模型来处理异步信号。

7> 调用setjmp(3C) 和longjmp(3C),然后长时间跳跃,而不释放互斥锁。

8> 从对*_cond_wait() 或*_cond_timedwait() 的调用中返回后无法重新评估条件。





总结

判断一个函数是不是可重入函数,在于判断其能否可以被打断,打断后恢复运行能够得到正确的结果。(打断执行的指令序列并不改变函数的数据)

判断一个函数是不是线程安全的,在于判断其能否在多个线程同时执行其指令序列的时候,保证每个线程都能够得到正确的结果。



如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。

如果函数对异步信号处理程序的重入是安全的,那么就可以说函数是"异步-信号安全"的。



参考:

CSAPP

13.7.1 线程安全

13.7.2 可重入性

《Advanced Programming in the UNIX Environment》2nd Editon

10.6 Reentrant Function

12.5 Reentrant

《多线程编程指南》

信号处理程序和异步信号安全

http://blog.chinaunix.net/u/25994/showart_369466.html

【C/C++】对于可重入、线程安全、异步信号安全几个概念的理解的更多相关文章

  1. Java 可重入锁的那些事(一)

    本文主要包含的内容:可重入锁(ReedtrantLock).公平锁.非公平锁.可重入性.同步队列.CAS等概念的理解 显式锁 上一篇文章提到的synchronized关键字为隐式锁,会自动获取和自动释 ...

  2. linux可重入、异步信号安全和线程安全

    一 可重入函数 当一个被捕获的信号被一个进程处理时,进程执行的普通的指令序列会被一个信号处理器暂时地中断.它首先执行该信号处理程序中的指令.如果从信号处理程序返回(例如没有调用exit或longjmp ...

  3. 可重入函数、线程安全、volatile

    一. POSIX 中对可重入和线程安全这两个概念的定义: Reentrant Function:A function whose effect, when called by two or more  ...

  4. 不可重入定时器Newlife.TimerX

    在.net常用的定时器类有下面三种,使用定时器时需要设定参数,如间断时间.定时器计溢出后的回调函数.延时.开始等,定时器的的主要方法有开始.终止等,不同的定时器实现上述的方法会有一些差异,本文会针对具 ...

  5. Use Reentrant Functions for Safer Signal Handling(译:使用可重入函数进行更安全的信号处理)

    Use Reentrant Functions for Safer Signal Handling 使用可重入函数进行更安全的信号处理 How and when to employ reentranc ...

  6. 重读APUE(11)-信号安全的可重入函数

    重入时间点 进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就会被信号处理程序临时中断,它首先执行该信号粗合理程序中的指令:如果从信号处理程序返回,则继续执行捕捉到信号时进程正在执行的正常指 ...

  7. SQL Server存储过程中防止线程重入处理方式

    对于线程重入,在C#中有lock关键字锁住一个SyncObject,而SQL Server也可用一个表来模拟实现. 先创建一个同步表,相当于C#中的SyncObject,并插入一条记录(初始值为1) ...

  8. Qt之可重入与线程安全

    简述 本篇文章中,术语"可重入性"和"线程安全"被用来标记类与函数,以表明它们如何被应用在多线程应用程序中. 一个线程安全的函数可以同时被多个线程调用,甚至调用 ...

  9. 可重入与线程安全(大多数Qt类是可重入,非线程安全的)

    可重入与线程安全 在Qt文档中,术语“可重入”与“线程安全”被用来说明一个函数如何用于多线程程序.假如一个类的任何函数在此类的多个不同的实例上,可以被多个线程同时调用,那么这个类被称为是“可重入”的. ...

随机推荐

  1. The Semantics of Constructors(拷贝构造函数之编译背后的行为)

    本文是 Inside The C++ Object Model's Chapter 2  的部分读书笔记. 有三种情况,需要拷贝构造函数: 1)object直接为另外一个object的初始值 2)ob ...

  2. 2019.12.12网页设计大赛&2019.12.13程序设计大赛观后感

    有幸参加了一次网页设计大赛和程序设计大赛,其实在大一的时候就参加过一次程序设计大赛,那时候也没怎么听,现在又有了一次机会来听,这次就认真的听了这两次的比赛,也有很多的感悟. 1.要学习完成一个任务的多 ...

  3. C. Planning(贪心)

    C. Planning time limit per test 1 second memory limit per test 512 megabytes input standard input ou ...

  4. 从零开始学习Gradle之三---多项目构建

       随着信息化的快速发展,IT项目变得越来越复杂,通常都是由多个子系统共同协作完成.对于这种多系统.多项目的情况,很多构建工具都已经提供了不错的支持,像maven.ant.Gradle除了借鉴了an ...

  5. python递归获取目录下指定文件

    获取一个目录下所有指定格式的文件是实际生产中常见需求. import os #递归获取一个目录下所有的指定格式的文件 def get_jsonfile(path,file_list): dir_lis ...

  6. TCP主动打开 之 第三次握手-发送ACK

    假定客户端执行主动打开,并且已经收到服务器发送的第二次握手包SYN+ACK,在经过一系列处理之后,客户端发送第三次握手包ACK到服务器:其流程比较简单,主要是分配skb,初始化ack包并发送:需要注意 ...

  7. MySQL5.7快速修改表中字段长度

    在mysql 5.5版本时,商用环境升级,有一个表存在六千多万数据,升级时需要修改这个表其中一个varchar类型字段的长度,当时用了大概4个多小时,还没有结束,之后我们系统mysql升级到5.7版本 ...

  8. C# 中使用RegisterShellHookWindow Hook窗体创建

    前言:最近在写一个桌面程序时需要全局HOOK 窗体的创建,但是在.net中SetWindowsHookEx()只可实现键盘鼠标的全局钩子,其余的全局钩子都需要使用DLL.难道就没有解决办法了么?经过长 ...

  9. [SQL 高级查询运算符的用法 UNION (ALL),EXCEPT(ALL),INTERSECT(ALL) ]

    今天看到 三个查询运算符,给大家分享分享 为此我建立了两张表分别为 Articles 和  newArticles 我建立的时候,只建立了一张表 Articles   ,表 newArticles 是 ...

  10. jQuery获取元素值以及设置元素值总结

    html(): 1:用户获取元素内的HTML内容,如果元素包含子标签,会以整体的形式返回 2:只获取第一个元素的内容 3:只获取普通元素的内容,表单元素内容无法获取 html(val): 1:用来设置 ...