线程安全的(Thread-Safe):如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。线程安全函数解决多个线程调用函数时访问共享资源的冲突问题。
可重入(Reentrant):函数可以由多于一个线程并发使用,而不必担心数据错误。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入性解决函数运行结果的确定性和可重复性。可重入函数编写规范为:
1、不在函数内部使用静态或全局数据
2、不返回静态或全局数据,所有数据都由函数的调用者提供。
3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
4、不调用不可重入函数。
两者之间的关系:
1、一个函数对于多个线程是可重入的,则这个函数是线程安全的。
2、一个函数是线程安全的,但并不一定是可重入的。
3、可重入性要强于线程安全性。
比如:strtok函数是既不可重入的,也不是线程安全的(strtok函数实现中用到了静态变量)。加锁的strtok不是可重入的,但线程安全。而strtok_r既是可重入的,也是线程安全的。
还有malloc和free是不可重入的,但是是线程安全的.
下面一个函数就是线程安全但却不可重入的
static int *sharedID; int* threadsafe_getID( char* name ) { int *unsharedID; P( &mutex ); sharedID = notThreadsafe_getID( name ); unsharedID = sharedID; V( & mutex); return unsharedID; }
|
上面的函数通过P、V操作封装得到一个线程安全函数,却不是可重入函数。
自己看了不少这方面的文章,感觉对线程安全和可重入之间的区别有了稍微的理解:
可重入必须没有使用共享的变量(如全局变量或者静态变量);而线程安全可以使用共享的变量,不过这些共享的变量要加锁和解锁来防止冲突。
在大部分情况下,不可重入的函数可以修改成可重入的函数,这需要修改函数的对外接口。
一、很多不可重入的函数返回一个指向静态数据的指针。要把这样的不可重入函数改写为可重入函数时,有两种解决办法:
1、返回从堆中分配的空间的地址。在这种情况下,调用者必须负责释放堆中的空间。这种办法的优点是不必修改函数的外部接口,但是不能向后兼容。现存的单线程的程序使用修改后的函数会导致内存泄露(因为它们没有释放空间)。
2、由调用者提供空间。尽管函数的外部接口需要改变,仍然推荐这种方法。
例如,一个strtoupper函数,一个字符串转换成大写,实现为:
/* non-reentrant function */
char *srttoupper(char *string)
{
static char buffer[MAX_STR_SIZE];
int index;
for(index=0;string[index];index++)
buffer[index]=toupper(string[index]);
buffer[index]=0;
return buffer;
}
上面函数不是线程安全的(当然也不是可重入的)。使用第一种方法将其改写为可重入的:
/* reentrant function (a poor solution)*/
char *strtoupper(char *string)
{
char *buffer;
int index;
buffer=malloc(MAX_STR_SIZE);
for(index=0;string[index];index++)
buffer[index]=toupper(string[index]);
buffer[index]=0;
return buffer;
}
使用第二种方法解决:
/* reentrant function (a better solution)*/
char *strtoupper_r(char *in_str,char *out_string)
{
int index;
for(index=0;in_str[index];index++)
out_str[index]=toupper(in_str[index]);
out_str[index]=0;
return out_str;
}
二、有一些不可重入函数为后继的调用保持数据。
一个可重入的函数不应该为后续的调用保持数据(即后继的调用和本次调用无关),因为下一次调用可能是由不同的线程调用的。如果一个函数需要在连续的调用之间维护一些数据,例如一个工作缓冲区或是一个指针,这些数据(资源)应该由调用这个函数的函数提供。
例如:返回指定字符串中的下一个小写字符的函数。字符串只有在第一次调用时被提供,就像strtok一样。到达末尾时返回0。
/* non-reentrant function*/
char lowercase_c(char *string)
{
static char *buffer;
static int index;
char c=0;
if(string!=NULL){
buffer=string;
index=0;
}
for(;c=buffer[index];index++){
if(islower(c)){
index++;
break;
}
}
return c;
}
该函数是不可重入的。要使它改写成可重入的,其中的静态数据应该由它的调用者维护。
/* reentrant function*/
char reentrant_lowercase_c(char *string,int *p_index)
{
char c=0;
for(;c=string[*p_index];(*p_index)++){
if(islower(c)){
(*p_index)++;
break;
}
}
return c;
}
要使一个非线程安全函数改为一个线程安全的函数,一种方法就是上面提到的把不可重入函数改成可重入函数,这样自然就把非线程安全变为线程安全了。另一方法是对住共享资源使用锁。
使用静态数据或者其它任何共享资源(如文件、终端等)的函数,必须对这些资源加“锁”以实现对它们的串行访问,这样才能成为线程安全的函数。例如:
/* thread-unsafe function*/
int increament_counter()
{
static int counter=0;
counter++;
return counter;
}
/* pseudo-code thread-safe function*/
int increment_counter()
{
static int counter=0;
static lock_type counter_lock=LOCK_INITIALIZER;
lock(counter_lock);
counter++;
unlock(counter_lock);
return counter;
}
阅读(1396) | 评论(0) | 转发(0) |