可重入,异步信号安全,线程安全

声明:本文为个人理解,不能保证一定是正确的!

  这三个概念一直纠缠着我,我也时不时的会拿出来辨析下,直到昨天才发现自己可以把它们理顺了。所以学习就是这样一个反复的过程,最终达到顿悟的效果。本文主要参考APUE第三版英文版第10.6和12.5节,以及WIKI百科,还有CSDN和stackoverflow中对这些概念的讨论,然后给出一份自己认为比较合理的理解。


中断,信号,线程切换

==========================

  1. 这三个概念都牵涉到异步通信,因为运行中的代码不可预测什么时候会发生中断,什么时候会收到信号,什么时候会发生线程切换:

    • 中断,一般指的硬件中断,是硬件对cpu的中断
    • 信号,则是对中断的模拟,可以看作是os对进程的中断
    • 线程切换,cpu的时分复用手段
  2. 代码执行流:

#-表示代码在cpu上运行,.表示等待中

##1. 中断和信号
<f1>---------...................-----------
<f2>.........-------------------...........
###f1在执行的过程中被f2打断,当且仅当f2完整的执行完后返回f1

##2. 线程切换
<f1>----....----....----....----....----
<f2>....----....----....----....----....
###f1和f2互相打断彼此,交错地运行

可重入,异步信息安全,线程安全

====================================

  1. 可重入
      可重入的意思就是一个函数没有执行完,又在另一个地方被调用一次,两次调用都能得到正确的结果。可重入概念在多任务操作系统之前就已经存在了,It is a concept from the time when no multitasking operating systems existed。

  2. 异步信息安全,线程安全
      可重入中提到的”另一个地方”可以是:

    • 在中断或信号中,在此情况下如果函数是可重入的,那么就称这个函数是异步信号安全的,这两种情况可以看成是一种递归调用
      [APUE10.6节就是专门说明异步信号安全的]
    • 在另一个线程中,在此情况下如果函数是可重入的,那么就称这个函数是线程安全的。
      [APUE12.5节就是专门说明线程安全的]

    If a function is reentrant with respect to multiple threads, we say that it is thread-safe. This doesn’t tell us, however, whether the function is reentrant with respect to signal handlers. We say that a function that is safe to be reentered from an asynchronous signal handler is async-signal safe.

3. 三者的关系

三者的关系
红色表示不可重入函数,A+B+C表示可重入函数,其中A表示递归调用情况下可重入,C表示多线程的情况下可重入,B表示种情况下都可重入。
所以单独说某函数是重入,而不限定使用场景是没有意义的;如果仅仅说是非可重入则又是有意义的…

4. 异步信号安全函数列表

异步信号安全

5. 可重入函数和异步信号安全函数等同吗?

根据上面的关系图可以得出两者是不等同的,可重入函数在不同的情景下可以分为异步信号安全和线程安全。异步信号安全可以看作是递归调用情况下的可重入。举例如下:

//非可重入版本
int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;

    // hardware interrupt might invoke isr() here!!
    *y = t;
}
/
//异步信号安全版本,非线程安全
int t;

void swap(int *x, int *y)
{
    int s;

    s = t; // save global variable
    t = *x;
    *x = *y;

    // hardware interrupt might invoke isr() here!
    *y = t;
    t = s; // restore global variable
}

//在信号处理函数中调用时,每次使用t之前都会进行备份,返回之前还原t的值;所以在信号处理函数中调用是可重入的;
//如果用在线程中,则有可能线程切换之前未来得及还原t的值,导致结果出错,所以在线程切换中是不可重入的;

//由此可以看出,异步信号安全函数不一定是线程安全函数!
//线程安全的函数由于可能使用互斥锁,在信号处理函数中递归调用会出现死锁,所以线程安全的函数也不一定是异步信号安全的。

其它

=====

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

    • 函数使用静态数据结构;
    • 函数调用malloc和free.因为malloc通常会为所分配的存储区维护一个链接表,而插入执行信号处理函数的时候,进程可能正在修改此链接表;
    • 函数是标准IO函数,因为标准IO库的很多实现都使用了全局数据结构;
    • 函数会修改自身代码,导致多次调用不同代码;
    • 等等。
  2. 即使对于可重入函数,在信号处理函数中使用也需要注意一个问题就是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可以在多线程中使用

  3. 很多函数并不是线程安全的,因为他们返回的数据是存放在静态的内存缓冲区中的。通过修改接口,由调用者自行提供缓冲区就可以使这些函数变为线程安全的。操作系统实现支持线程安全函数的时候,会对POSIX.1中的一些非线程安全的函数提供一些可替换的线程安全版本。
    例如,gethostbyname()是线程不安全的,在Linux中提供了gethostbyname_r()的线程安全实现。
    函数名字后面加上”_r”,以表明这个版本是可重入的(对于线程可重入,也就是说是线程安全的,但并不是说对于信号处理函数也是可重入的,或者是异步信号安全的)。

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

    • 将指针作为新线程的参数传递给调用方栈,我就犯过这样的错…
    • 在没有同步机制保护的情况下访问全局内存的共享可更改状态。
    • 两个线程尝试轮流获取对同一对全局资源的权限时导致死锁。其中一个线程控制第一种资源,另一个线程控制第二种资源。其中一个线程放弃之前,任何一个线程都无法继续操作。
    • 尝试重新获取已持有的锁(递归死锁)。
    • 在同步保护中创建隐藏的间隔。如果受保护的代码段包含的函数释放了同步机制,而又在返回调用方之前重新获取了该同步机制,则将在保护中出现此间隔。结果具有误导性。对于调用方,表面上看全局数据已受到保护,而实际上未受到保护。
    • 将UNIX 信号与线程混合时,使用sigwait(2) 模型来处理异步信号。
    • 调用setjmp(3C) 和longjmp(3C),然后长时间跳跃,而不释放互斥锁。
    • 从对*_cond_wait() 或*_cond_timedwait() 的调用中返回后无法重新评估条件。
  5. 如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。
    如果函数对异步信号处理程序的重入是安全的,那么就可以说函数是”异步-信号安全”的。


参考文档

==============
1. APUE第三版原版10.6&12.5,去阅读原版而不是中文版(有道在手,天下我有:P)
2. https://en.wikipedia.org/wiki/Reentrancy_(computing)
3. http://bbs.csdn.net/topics/310140183
4. http://stackoverflow.com/questions/9837343/difference-between-thread-safe-and-async-signal-safe
5. http://stackoverflow.com/questions/18198487/are-r-unix-calls-reentrant-async-signal-safe-thread-safe-or-both

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值