多线程之Thread-safe和Async-signal safe分析(十二)

1. 写在前面

笔者刚开始看到thread-safeasync-signal safe,非常草率的就过去了。笔者在开始本文前的理解:不就是重入嘛,当一个函数被调用的时候,又调用了该函数。在异步信号发生的场合对应着async-singal safe,在多线程中发生的场合对应着thread-safe 如果您的理解和笔者一样,那么本文值得您浏览一下。

2. Async-signal Safe

2.1 MAN Page

以下内容来自MAN Page

An async-signal-safe function is one that can be safely called from within a signal handler. Many functions are not async-ignal-safe. In particular, nonreentrant functions are generally unsafe to call from a signal handler.

接下来MAN Page指出,STDIO库中的函数都不是singal safe。因为其静态的分配了buffer和其他用来指示状态的变量,那么如果main中调用其中一个标准IO函数,而在该函数执行期间被信号中断,而在中断处理函数函数中又调用了该函数,必然将覆盖以前静态分配区域的数据,从而导致我们不期望的结果。对此MAN Page给出的策略就是:

  • Ensure that (a) the signal handler calls only async-signal-safe functions, and (b) the signal handler itself is reentrant with respect to global variables in the main program.
  • Block signal delivery in the main program when calling functions that are unsafe or operating on global data that is also accessed by the signal handler.

其中第二个措施并不容易实现,稍微有点复杂度的程序,信号是必须采用的手段,MAN Page给出了一个很准确的描述:

In general, a function is async-signal-safe either because it is reentrant or because it is atomic with respect to signals

NOTE: POSIX.1-2003 clarified that if an application calls fork(2) from a signal handler and any of the fork handlers registered by pthread_atfork(3) calls a function that is not async-signal-safe, the behavior is undefined. A future revision of the standard is likely to remove fork(2) from the list of async-signal-safe functions.

异步信号安全的函数即可以是可重入的,或者对于信号处理函数来说其调用是原子操作,或者两者都有。对于在信号处理函数中调用fork,此时pthread_atfork注册的三个handler中都不能调用非信号安全的函数,否则会发生无法预测的行为,笔者语:这样的限制其实蛮大的。因为无论是parperparent或者childhandler都将在signal handler的上下文执行。(signal handler是多线程及多进程共享的)

2.2 Google Site

Google Site 这篇文章比较详细的论述了这个问题,笔者从中节选出来比较合适的部分:

A function is asynchronous-safe, or asynchronous-signal safe, if it can be called safely and without side effects , without interfering other operations, from within a signal handler context. That is, it must be able to be interrupted at any point to run linearly out of sequence without causing an inconsistent state. It must also function properly when global data might itself be in an inconsistent state.

以上节选来自GUNasynchronous-safe的定义,言简意赅的说就是符合信号安全的函数,要保证在该函数执行期间的任何位置,信号处理函数中对该函数的调用都不会产生任何问题。

2.3 APUE

笔者在信号篇的总结中,总结比较不全面。APUE中使用了getpwnam做了个实验,结果程序有时候会意外终止,APUE中提到以下几个特点的函数一定不是信号安全的:

  • they are known to use static data structures
  • they call malloc or free
  • they are part of the standard I/O library

3. Thread Safe

3.1 Wikipedia
  • Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfil their design specifications without unintended interaction.
  • Thread safety is a property that allows code to run in multithreaded environments by re-establishing some of the correspondences between the actual flow of control and the text of the program, by means of synchronization.

第一点比较容易理解,第二点的话稍微有点绕可以这么理解:使用同步的方式来使实际的执行流和程序本身设计的内容相一致,也就是保证每个线程运行该段代码都能到达预期的目的。笔者语:线程安全,不仅仅考虑该函数可以多线程重入,也要考虑并发执行效率。维基百科中举出了几个实现线程安全的措施:

  • The first class of approaches focuses on avoiding shared state, and includes: Re-entrancy,Thread-local storage,Immutable objects
  • The second class of approaches are synchronization-related, and are used in situations where shared state cannot be avoided:Mutual exclusion, Atomic operations

接着看看维基百科中给出的一组C代码:

# include <pthread.h>

int increment_counter ()
{
 static int counter = 0;
 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

 // only allow one thread to increment at a time
 pthread_mutex_lock(&mutex);

 ++counter;

 // store value before any other threads increment it further
 int result = counter;

 pthread_mutex_unlock(&mutex);

 return result;
}

以上的代码非常简单,对于多线程并发来说其一定是安全的,但是其不可重入。因为会造成死锁,所以这里笔者终于转过弯了:可重入对信号安全和线程安全的要求是不一样的,信号安全里面的可重入排除了一种使用mutex 及相关的机制,而线程安全的可重入却可以使用mutex。所以可重入的函数一定是thread safe,但是thread safe的函数并不一定是可重入的。

3.2 Man Page

A thread-safe function is one that can be safely (i.e., it will deliver the same results regardless of whether it is) called from multiple threads at the same time.

MAN Page中关于thread safe的记录只是简单的说如果允许多个thread同时调用该函数,且不会造成任何不希望的结果,则其是thread-safe

3.3 APUE

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.

以上表明了一个函数重入的要求对于信号安全和线程安全来说是不同的。保证线程安全,却不一定能保证信号安全,但是如果一个信号安全的函数,其一定是线程安全的。

#include <stdio.h>
int ftrylockfile(FILE *fp);
Returns: 0 if OK, nonzero if lock can’t be acquired
void flockfile(FILE *fp);
void funlockfile(FILE *fp);

int getchar_unlocked(void);
int getc_unlocked(FILE *fp);
Both return: the next character if OK, EOF on end of file or error
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
Both return: c if OK, EOF on error

以上API其实与本篇主体关系不大,举例来说getchar_unlockedgetchar的区别仅仅在于是内部使用过了lock,不加锁版本是为了减少自带锁版本的每次处理频繁的加锁解锁的消耗。从一定意义上讲后缀有_unlocked的函数是非Thread Safe的。

4. 结论

最后,总结一下前文的内容:重入对于异步信号安全和线程安全的要求是不同的,满足异步信号安全就一定能满足线程安全。实现线程安全的方式有多种,使用原子操作,互斥锁,及使用只读变量等。但是实现信号安全的方式就有限制了,因为有可能发生死锁,所以互斥机制就不太适合。而且更为重要的是要保证信号安全的函数里面其他的函数一定都是信号安全的。

**NOTE : ** 如果您对笔者上述的论述有所疑问,请及时指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值