分类:
2008-12-16 10:20:54
12.5 reentrancy
(一)reentrancy 和thread safty 以及 async-signal safety 的关系
Reentrant function并不是一个确定的概念,我们可以说这个函数是thread reentrant function,所以它是一个thread safe function.意思是说这个函数是可以被别的thread同时调用的。我们还可以说另一个函数是signal reentrant function, 所以它是一个async signal safe function。意思是说这个函数可以被signal打断,然后再signal handler里面再次调用它。所以当我们说一个函数是reentrant函数的时候,要指明是针对thread,还是针对signal。如果我们不知名的话,我们的意思就是说全部都包括。即既thread reentrant又ssync-signal
reentrant。这就是广义的reentrant function.
广义的reentrant function的定义:(摘自wiki reentrant(subroutine))
看到没,倒数第2点,不需使用锁来访问一些资源,所以一个thread safe的function就可能被排除在外了,因为thread reentrant(thread safe)函数有可能就是靠锁来实现的。所以thread reentrant(thread safe) 函数并不算严格意义的reentrant函数,因为它不能做到async-signal reentrant。
所以:
1.一个reentrant函数一定是thread safe的
2.一个thread
safe的函数不一定是reentrant的。比如你是thread safe的,但不是async-signal safe的,看上边的死锁的例子。
Thread和signal的区别:
1. Thread是并行执行的, 特别是对于多cpu的环境,是真正的并行执行
2.Signal
handler与被signal中断的程序不是并行的,被中断的程序stop, signal handler执行完后,才继续。
由于上述特点,thread可以用锁来保证同步,可以使我们的函数thread reentrant,即threa
safe. 但是,signal
handler就不一定可以,特别是如果你在你的一个函数里,已经加了普通锁,signal 发生,程序被打断,handler执行,handler里面又加锁,这就会造成死锁(前提是你使用的不是recursive锁)。可见signal reentrant要比thread
reentrant更严格。
举个例子:你的正在调用printf函数,她正在处理标准输出,突然收到信号,在handler里面也调用printf,那么你就可能死锁。因为printf函数为了实现thread safe, 在里面自己给FILE加锁了。在你释放之前,又调用printf去加锁当然就死锁了(前提是我们使用的不是recursive锁)。
(二)针对stdandard i/o library要做到thread safe的方法
给FILE object中加入了一个lock,普通的standard
i/o library都会首先给这个FILE object加锁,然后再去处理,这个锁是recurive锁。我们可以假设所有的standard i/o 函数都会自动加锁和释放锁,来处理FILE对象,所以我们知道standard i/o library是thread reentrant的,也就是thread safe的。
#include int ftrylockfile(FILE *fp); |
Returns: 0 if OK, nonzero if lock can't be acquired |
void flockfile(FILE *fp); void funlockfile(FILE *fp); |
这就是他们使用的加锁函数,但是这个函数既然是standard i/o library内部使用的,那为什么将他们expose给用户呢?因为有时候,用户需要手动根据特殊情况修改对FILE object的加锁情况。比如,用户想手动来控制加锁以提高性能:
1.我想将若干个standard i/o library的操作序列执行完后才允许别的thread来进行操作时,就可以先手动加锁,然后调用若干standard i/o 的函数,然后再释放锁。
2.有些使用频率很高的standard i/o函数,每次调用都加锁/解锁,性能会下降。所以我们可以手动加锁,然后调用n次不自动加锁的standard i/o函数,然后再释放锁。于是就有了不自动加锁的standard i/o function版本。
比如Getch函数,本身只处理一个字符,如果每次调用都加锁再释放,而且调用频率极高,那么就会性能下降。我们可以将若干个不自动加锁的getch联合起来,一起加锁,比如加锁后,调用10次getch再释放锁。
#include 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 |
这就使不自动加锁的getchar函数版本。
(三)一些网上的资源
1.Writing signal-safe code: 通过举例的方式阐述清楚了thread safe和signal safe的区别和如何写signal safe的代码的探讨
http://www.cocoadev.com/index.pl?SignalSafety
2. Use reentrant functions for safer signal handling
http://www.ibm.com/developerworks/linux/library/l-reent.html