分类: LINUX
2012-02-01 21:35:59
++++++APUE读书笔记-12线程控制-05可重入性++++++
5、可重入性
================================================
前面我们讨论过了和signal相关的函数的可重入问题,在线程中同样会有这样的问题。
如果一个函数可以被多个线程在同时被调用,那么我们就说这个函数是线程安全的。
下面列表,列出了哪些函数不是线程安全的。
POSIX.1中定义的不是线程安全的函数
+-------------------------------------------------------------------------------------+
| asctime | ecvt | gethostent | getutxline | putc_unlocked |
|--------------+------------------+------------------+-------------+------------------|
| basename | encrypt | getlogin | gmtime | putchar_unlocked |
|--------------+------------------+------------------+-------------+------------------|
| catgets | endgrent | getnetbyaddr | hcreate | putenv |
|--------------+------------------+------------------+-------------+------------------|
| crypt | endpwent | getnetbyname | hdestroy | pututxline |
|--------------+------------------+------------------+-------------+------------------|
| ctime | endutxent | getnetent | hsearch | rand |
|--------------+------------------+------------------+-------------+------------------|
| dbm_clearerr | fcvt | getopt | inet_ntoa | readdir |
|--------------+------------------+------------------+-------------+------------------|
| dbm_close | ftw | getprotobyname | l64a | setenv |
|--------------+------------------+------------------+-------------+------------------|
| dbm_delete | gcvt | getprotobynumber | lgamma | setgrent |
|--------------+------------------+------------------+-------------+------------------|
| dbm_error | getc_unlocked | getprotoent | lgammaf | setkey |
|--------------+------------------+------------------+-------------+------------------|
| dbm_fetch | getchar_unlocked | getpwent | lgammal | setpwent |
|--------------+------------------+------------------+-------------+------------------|
| dbm_firstkey | getdate | getpwnam | localeconv | setutxent |
|--------------+------------------+------------------+-------------+------------------|
| dbm_nextkey | getenv | getpwuid | localtime | strerror |
|--------------+------------------+------------------+-------------+------------------|
| dbm_open | getgrent | getservbyname | lrand48 | strtok |
|--------------+------------------+------------------+-------------+------------------|
| dbm_store | getgrgid | getservbyport | mrand48 | ttyname |
|--------------+------------------+------------------+-------------+------------------|
| dirname | getgrnam | getservent | nftw | unsetenv |
|--------------+------------------+------------------+-------------+------------------|
| dlerror | gethostbyaddr | getutxent | nl_langinfo | wcstombs |
|--------------+------------------+------------------+-------------+------------------|
| drand48 | gethostbyname | getutxid | ptsname | wctomb |
+-------------------------------------------------------------------------------------+
如果实现支持线程安全的函数,那么需要在unistd.h中定义变量_POSIX_THREAD_SAFE_FUNCTIONS,当然我们也可以在运行的时候使用sysconf带参数_SC_THREAD_SAFE_FUNCTIONS来检测是否支持线程安全函数。所有遵从XSI标准的实现都要求支持线程安全函数。
当支持线程安全函数特性的时候,实现会为POSIX.1的一些非线程安全函数提供一个可选则的线程安全的版本。下表列出了一些线程安全的函数。
一些可选的线程安全的函数
+----------------------------+
| acstime_r | gmtime_r |
|-------------+--------------|
| ctime_r | localtime_r |
|-------------+--------------|
| getgrgid_r | rand_r |
|-------------+--------------|
| getgrnam_r | readdir_r |
|-------------+--------------|
| getlogin_r | strerror_r |
|-------------+--------------|
| getpwnam_r | strtok_r |
|-------------+--------------|
| getpwuid_r | ttyname_r |
+----------------------------+
有许多函数并不是线程安全的,因为它们使用了静态内存来存放返回的数据。通过修改它们的接口,使用它们自己的缓存,来让它们线程安全。上表中的函数就是在原来的函数名称后面加上了一个_r后缀,这样表示它们是线程安全的。
如果一个函数在多线程环境下面是可重入的,那么我们把这个函数称作是thread-safe的,但是这并不意味这个函数是在信号处理条件下面也可重入。如果一个函数在异步信号处理函数中也是可重入的,那么我们把这个函数称作是async-signal的。
除了前面所说的,POSIX.1提供了一种可以线程安全地管理文件对象的方法。你可以使用flockfile和ftrylockfile函数获得一个和给定的文件对象相互关联的锁。这个锁是递归锁。尽管这个锁的具体实现是不确定的,但是要求所有标准输入输出函数管理文件对象的时候,表现的都像人安眠药非已经在内部调用了flockfile和funlockfile一样。
#include
int ftrylockfile(FILE *fp);
如果成功返回0,如果锁无法获得则返回非0。
void flockfile(FILE *fp);
void funlockfile(FILE *fp);
尽管标准输入输出函数在他们自己的内部数据来看是线程安全的,但是能够将相关的锁暴露出来也是很有用的。这允许应用程序把多个标准输入输出调用合并成一个单个的原子操作序列,当然在处理多个文件对象的时候,你需要注意文件死锁的问题,仔细管理你上锁的次序。
在进行字符输入输出的时候,如果标准输入输出函数请求它们自己的锁那么会导致性能下降很多,因为每次进行一个字符的读写的时候,我们都得进行锁的申请和释放。为了改善这种状况,有一对非上锁的面向字符的输入输出操作函数,如下:
#include
int getchar_unlocked(void);
int getc_unlocked(FILE *fp);
两个函数如果成功都会返回下一个字符,如果失败就会返回文件的结尾EOF。
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
两个函数如果成功都会返回c,如果失败会返回错误。
调用这四个函数,需要在它们的附近调用flockfile或者ftrylockfile以及funlockfile。否则,会出现无法预测的结果(多个线程访问数据出现的非同步的问题)。
当你锁住文件对象的时候,你可以在释放锁的之前多次调用这些函数,这样可以减少上锁和释放锁的开销。
例子
参考资料中的12.11给出了一个getenv的非线程安全版本和线程安全版本。
具体参见其中的代码。主要需要注意的是:
非可重入版本(非线程安全)的getenv把东西存放在了其函数的一个静态变量中,这样所有的线程都共享这个静态变量。所以修改了这个函数的接口,让它使用自己的缓存存放相应的内容,而不是静态变量,这样就成了线程安全的(也就是多线程可重入的)getenv函数也就是getenv_r。
注意虽然是多线程下可重入,但是不一定信号处理的方面也是可重入的。
还有一个需要注意的地方,就是我们使用了一个pthread_once函数来调用某个函数(thread_init),保证这个函数(thread_init)在一个进程中只被执行一次。
参考: