Chinaunix首页 | 论坛 | 博客
  • 博客访问: 252732
  • 博文数量: 58
  • 博客积分: 2241
  • 博客等级: 大尉
  • 技术积分: 522
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-07 09:53
文章分类
文章存档

2012年(4)

2011年(19)

2010年(31)

2009年(4)

分类: C/C++

2010-12-06 09:47:34


在多线程或有异常控制流的情况下,当某个函数运行到中途时,控制流(也就是当前指令序列)就有可能被打断而去执行另一个函数.而"另一个函数"很有可能是它本身.

如果在这种情况下不会出现问题,比如说数据或状态不会被破坏,行为确定。那么这个函数就被称做"可重入"的.
补充:
函数是可重入(reentrant)的,是指对于相同的(并且合法的)函数参数(包括无参函数的情况),多次调用此函数产生的行为是可预期的,即函数的行为一致,或者结果相同。不能保证这一点的函数称为不可重入(non-reentrant)函数。
可重入和线程安全(Thread-Safe)是两个不同的概念:可重入函数一定是线程安全的;线程安全的函数可能是重入的,也可能是不重入的;线程不安全的函数一定是不可重入的。

可重入函数:     Reentrant Function
线程安全函数:   Thread-Safe Function
可重入和线程安全不是一个概念。
可重入 => 线程安全

可重入函数要解决的问题是,不在函数内部使用静态或全局数据,不返回静态或全局数据,也不调用不可重入函数。

线程安全函数要解决的问题是,多个线程调用函数时访问资源冲突。

函数如果使用静态变量,通过加锁后可以转成线程安全函数,但仍然有可能不是可重入的,比如strtok。

strtok是既不可重入的,也不是线程安全的。

加锁的strtok不是可重入的,但线程安全。

而strtok_r既是可重入的,也是线程安全的。
3. reentrant函数与thread safe函数的区别

reentrant函数与是不是多线程无关,如果是reentrant函数,那么要求即使是同一个进程(或线程)同时多次进入该函数时,该函数仍能够正确的运作.
该要求还蕴含着,如果是在多线程环境中,不同的两个线程同时进入该函数时,该函数也能够正确的运作.

thread safe函数是与多线程有关的,它只是要求不同的两个线程同时对该函数的调用在逻辑上是正确的.

从上面的说明可以看出,reentrant的要求比thread safe的要求更加严格.reentrant的函数必是thread safe的,而thread safe的函数
未必是reentrant的
结论:
1. reentrant是对函数相当严格的要求,绝大部分函数都不是reentrant的(APUE上有一个reentrant函数
的列表).
什么时候我们需要reentrant函数呢?只有一个函数需要在同一个线程中需要进入两次以上,我们才需要
reentrant函数.这些情况主要是异步信号处理,递归函数等等.(non-reentrant的递归函数也不一定会
出错,出不出错取决于你怎么定义和使用该函数). 大部分时候,我们并不需要函数是reentrant的.

2. 在多线程环境当中,只要求多个线程可以同时调用一个函数时,该函数只要是thread safe的就可以了.
我们常见的大部分函数都是thread safe的,不确定的话请查阅相关文档.

3. reentrant和thread safe的本质的区别就在于,reentrant函数要求即使在同一个线程中任意地进入两次以上,
也能正确执行.

大家常用的malloc函数是一个典型的non-reentrant但是是thread safe函数,这就说明,我们可以方便的
在多个线程中同时调用malloc,但是,如果将malloc函数放入信号处理函数中去,这是一件很危险的事情.

4. reentrant函数肯定是thread safe函数,也就是说,non thread safe肯定是non-reentrant函数
不能简单的通过加锁,来使得non-reentrant函数变成 reentrant函数
"使用的全局变量的函数也不一定是不可重入的。"这句是正确的,只要正确使用就可以了,
但是不使用全局变量是写可重入函数的简单方法.
"调用了不可重入函数的函数不一定是不可重入的。"这句是不对的,
因为你无法保证被调用的不可重入函数部分不被重入

这个例子非常好,线程安全但不是可重入的:

int length = 0;
char *s = NULL;

// Note: Since strings end with a 0, if we want to
// add a 0, we encode it as "\0", and encode a
// backslash as "\\".

// WARNING! This code is buggy - do not use!

void AddToString(int ch)
{
EnterCriticalSection(&someCriticalSection);
// +1 for the character we're about to add
// +1 for the null terminator
char *newString = realloc(s, (length+1) * sizeof(char));
if (newString) {
if (ch == '\0' || ch == '\\') {
AddToString('\\'); // escape prefix
}
newString[length++] = ch;
newString[length] = '\0';
s = newString;
}
LeaveCriticalSection(&someCriticalSection);
}


阅读(1284) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~