Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1074716
  • 博文数量: 242
  • 博客积分: 10209
  • 博客等级: 上将
  • 技术积分: 3028
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-12 09:27
文章分类

全部博文(242)

文章存档

2014年(1)

2013年(1)

2010年(51)

2009年(65)

2008年(124)

我的朋友

分类: C/C++

2009-11-25 15:55:04

线程安全的(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;
}


阅读(1356) | 评论(0) | 转发(0) |
0

上一篇:偶成

下一篇:Singleton模式(一) ZZ

给主人留下些什么吧!~~