线程重入

线程在遇到重入问题时与信号处理程序是类似的,在这两种情况下,多个控制线程在相同的时间有可能调用相同的函数。如果一个函数在相同的时间点可以被多个线程安全地调用,就称该函数是线程安全的。在 Single UNIX Specification 定义的函数中,下面这些是[color=red]不能[/color]保证线程安全的(另外,ctermid 和 tmpnam 传入空指针参数时也不能保证线程安全)。
[img]http://dl2.iteye.com/upload/attachment/0127/9311/57e9cf20-12a5-3f92-94d1-b968b8f8a58b.png[/img]
支持线程安全函数的操作系统会在<unistd.h>中定义符号_POSIX_THREAD_SAFE_FUNCTIONS。应用程序也可以在 sysconf 函数中传入 _SC_PTHREAD_SAFE_FUNCTIONS 参数在运行时检查是否支持线程安全函数。操作系统实现支持线程安全函数这个特性时,对 POSIX.1 中的一些非线程安全函数,它会提供可替代的线程安全版本。下表列出了这些函数的线程安全版本,它们在原来的名字后面加了“_r”,表明这些版本是可重入的(很多函数并不是线程安全的,因为它们返回的数据存放在静态的内存缓冲区中。通过修改接口,要求调用者自己提供缓冲区可以使函数变为线程安全)。
[img]http://dl2.iteye.com/upload/attachment/0127/9313/65f72ada-77f5-3788-9ca0-6adb2b7e6357.png[/img]
如果一个函数对多个线程来说是可重入的,则该函数是线程安全的,但这并不能说明对信号处理程序来说也是可重入的。如果函数对异步信号处理程序的重入是安全的,则可以说函数是异步信号安全的,在[url=http://aisxyz.iteye.com/admin/blogs/2394900]信号默认处理动作及可重入函数[/url]一节中提到的可重入函数就是异步信号安全的。
下面给出了一个 getenv 的可重入版本 getenv_r,它使用 pthread_once 函数来确保不管多少线程同时竞争调用 getenv_t,每个进程都只调用 thread_init 函数一次。

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

pthread_mutex_t env_mutex;
static pthread_once_t initflag = PTHREAD_ONCE_INIT;

static void thread_init(void){
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&env_mutex, &attr);
pthread_mutexattr_destroy(&attr);
}

extern char **environ;

int getenv_r(const char *name, char buf[], int buflen){
// buf[0] = '\0';
int len = strlen(name);
pthread_once(&initflag, thread_init);
pthread_mutex_lock(&env_mutex);
int i = 0;
for(; environ[i] != NULL; i++){
if(strncmp(name, environ[i], len)==0 && environ[i][len]=='='){
if(strlen(&environ[i][len+1]) < buflen){
strcpy(buf, &environ[i][len+1]);
pthread_mutex_unlock(&env_mutex);
return 0;
}else{
pthread_mutex_unlock(&env_mutex);
return ENOSPC;
}
}
}
pthread_mutex_unlock(&env_mutex);
return ENOENT;
}

这里为了使该函数可重入而改变了其接口,调用者需要提供自己的缓冲区。而为了保证线程安全,使用了互斥量来保护环境在搜索请求的字符时不被修改。不仅如此,这里使用的还是可递归的互斥量,以保证它对信号处理程序也是可重入的。如果使用的是非递归的互斥量,则线程从信号处理程序中再用到该互斥量时就有可能出现死锁,因为在 getenv_r 中可能已经对其加锁了。
除了上表中的函数,POSIX.1 还提供了以线程安全的方式管理 FILE 对象的方法。可以使用 flockfile 和 ftrylockfile 获取给定 FILE 对象关联的锁,这个锁是递归的,这表示当占有这把锁的时候还可以再次获取该锁。

#include <stdio.h>
int ftrylockfile(FILE *fp);
/* 返回值:若成功,返回 0;若不能获取锁,返回非 0 数值 */
void flockfile(FILE *fp);
void funlockfile(FILE *fp);

如果标准 I/O 例程都获取它们各自的锁,那么在做一次一个字符的 I/O 时就会出现严重的性能下降,因为这需要对每一个字符的读写操作进行获取锁和释放锁的动作。为避免这种开销,出现了如下不加锁版本的基于字符的标准 I/O 例程(不过除非被上面的 flockfile 类函数的调用包围,否则尽量不要使用,因为它们会导致不可预期的结果,比如由于多个控制线程非同步访问数据引起的种种问题)。

#include <stdio.h>
int getchar_unlocked(void);
int getc_unlocked(FILE *fp);
/* 两个函数的返回值:若成功,返回下一个字符;若遇到文件尾或者出错,返回 EOF */
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
/* 两个函数的返回值:若成功,返回 c;否则,返回 EOF */

一旦对 FILE 对象进行加锁,就可以在释放锁前对这些函数进行多次调用,这样就可以在多次的数据读写上分摊总的加解锁的开销。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值