http://blog.csdn.net/littlehedgehog/archive/2009/04/23/4104210.aspx
http://docs.sun.com/app/docs/doc/816-5137/6mba5vqbd?l=en&a=view#gen-95948
http://hi.baidu.com/firobd/item/e45d7f8a68005f57840fabe2
http://blog.sina.com.cn/s/blog_8fa7dd4101015hi5.html
对于可重入、线程安全、异步信号安全几个概念的理解
可重入与异步信号安全一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误。
《多线程编程指南》中定义,可以被信号控制器安全调用的函数被称为"异步信号安全"函数。
因此,我认为可重入与异步信号安全是一个概念。
有人将可重入函数与线程安全函数混为一谈,我认为是不正确的。
这里引用CSAPP(深入理解计算机系统)中的描述来说明一下:
--------------------------------------------------
CSAPP
13.7.1 线程安全
一个函数被称为线程安全的,当且仅当被多个并发线程反复的调用时,它会一直产生正确的结果。
13.7.2 可重入性
有一类重要的线程安全函数,叫做可重入函数,其特点在于它们具有一种属性:当它们被多个线程调用时, 不会引用任何共享的数据。
尽管线程安全和可重入有时会(不正确的)被用做同义词,但是它们之间还是有清晰的技术差别的。 可重入函数是线程安全函数的一个真子集。
--------------------------------------------------
Writing Reentrant and Thread-Safe Code
在单线程程序中,只有单一控制流,程序所执行的代码不必是可重入或线程安全的。在多线程程序中,同一函数和同一资源有可能被多个控制流并发访问。为了保证资源的完整性,多线程程序中所使用的代码必须是可重入和线程安全的。
本节提供了编写可重入和线程安全程序的相关信息。然而本节的主题并不是如何编写高效并行化的多线程程序,这只有在程序设计阶段才能完成。现有的单线程程序必须彻底的重新设计和重新编写,才能实现高效线程化。
理解可重入与线程安全
可重入与线程安全这两个概念,都与函数处理资源的方式有关。可重入与线程安全是两个独立的概念,一个函数可以是可重入或是线程安全,或是同时满足两者,或是同时不满足两者的。
可重入
一个可重入的函数在执行中并不使用静态数据,也不返回指向静态数据的指针。所有使用到的数据都由函数的调用者提供。可重入函数在函数体内不能调用非可重入函数。
一个非可重入函数通常(尽管不是所有情况下)由它的外部接口和使用方法即可进行判断。例如
线程安全
一个线程安全的函数通过加锁的方式来实现多线程对共享数据的安全访问。线程安全这个概念,只与函数的内部实现有关,而不影响函数的外部接口。
在C语言中,局部变量是在栈上分配的。因此,任何未使用静态数据或其他共享资源的函数都是线程安全的。例如,下面的函数是线程安全的:
int diff(int x, int y)
{
}
使用全局变量(的函数)是非线程安全的。这样的信息应该以线程为单位进行存储,这样对数据的访问就可以串行化。一个线程可能会读取由另外一个线程生成的错误代码。在AIX中,每个线程有独立的errno变量。
函数可重入化
在多数情况下,非可重入的函数必须被修改过的具有可重入接口的函数所替代。非可重入函数不可用于多线程环境。此外,一个非可重入的函数可能无法满足线程安全的要求。
很多非可重入函数返回指向静态数据的指针。可以以两种方式避免这种情况:
例如,将字符串大写化的strtoupper()函数,实现如下:
char *strtoupper(char *string)
{
}
上面的函数是非可重入(也是非线程安全的)。运用之前介绍的第一种方法将函数改写为可重入函数,代码如下:
char *strtoupper(char *string)
{
}
更佳的改写方式是改变函数的外部接口。调用者必须为输入和输出字符串提供存储空间,代码如下:
char *strtoupper_r(char *in_str, char *out_str)
{
}
非可重入的C标准库是按照第二种方法改写的。这一点会在后文提到。
在连续的函数调用之间,不应该由函数保存任何信息,因为多个线程可能一个接一个的调用该函数。如果一个函数需要在连续的调用中保存某个信息,例如工作缓存区或是指针,这个信息应该由调用者负责保存。
考虑下面的例子。lowercase_c函数在连续调用中返回字符串中字符的小写字符。与strtok()函数的使用方法类似,该字符串只在函数第一次调用时作为参数提供。函数在到达字符串尾部时返回值为0。函数的实现代码如下:
char lowercase_c(char *string)
{
}
该函数是非可重入的。为了将其改写为可重入函数,由函数的静态变量index所保存的信息,应该改为由调用者负责保存。函数的可重入版本实现如下:
char reentrant_lowercase_c(char *string, int *p_index)
{
}
函数的外部接口和使用方法都需要修改。调用者必须在每次调用函数时提供字符串参数,并且在第一次调用前将index变量初始化为0,正如以下代码所展示的:
char *my_string;
char my_char;
int my_index;
...
my_index = 0;
while (my_char = reentrant_lowercase_c(my_string,&my_index)) {
}
函数线程安全化
在多线程程序中,所有被多个线程调用的函数都要求是线程安全的。然而,有一种方法能够实现在多线程程序中调用非线程安全的函数。同样需要注意的是,非可重入的函数通常也是非线程安全的,然而将其改写为可重入后,同时也就变为线程安全的了。
使用静态数据或其他共享资源(如文件、终端)的函数,必须通过加锁的方式来将对资源的访问串行化来实现线程安全。例如,下面的函数是非线程安全的。
int increment_counter()
{
}
为了实现线程安全,需要用一个静态锁来限制对静态变量counter的访问,如下面的代码所示(伪代码)
int increment_counter();
{
}
在使用线程库的多线程应用程序中,应该是用互斥锁来实现共享资源访问的串行化。独立的库有可能在线程之外的上下文环境中工作,因此,需要使用其他类型的锁。
使用非线程安全函数的解决方法
通过某种解决方法,非线程安全函数是可以被多个线程调用的。这在某些情况下或许是有用的,特别是当在多线程程序中使用一个非线程安全函数库的时候——或者是出于测试的目的,或者是由于没有相应的线程安全版本可用。这种解决方法会增加开销,因为它需要将对某个或一组函数的调用进行串行化。
由于该类解决方式只应该在应用程序而不是函数库中使用,可以使用互斥锁(mutex)来为整个库加锁。
可重入和线程安全函数库
可重入和线程安全函数库,不仅在多线程环境,在并行以及异步编程的广泛领域中也是很有用的。因此,坚持使用和编写可重入和线程安全函数是一个很好的编程习惯。
使用函数库
AIX base OS附带函数库中有几个是线程安全的。目前的AIX版本中,以下函数库是线程安全的:
某些C标准库函数是非可重入的,例如ctime()和strtok()。这些函数的对应可重入版本的名字为原函数加_r后缀
在编写多线程程序时,应该使用可重入版本的库函数替代原始版本。例如,下面的代码:
token[0] = strtok(string, separators);
i = 0;
do
{
} while (token[i] != NULL);
在一个多线程程序中应该替换成下面的代码:
char *pointer;
...
token[0] = strtok_r(string, separators,&pointer);
i = 0;
do
{
} while (token[i] != NULL);
非线程安全的函数库在程序中可以仅由一个线程使用。程序员必须保证使用该函数的线程的唯一性;否则,程序将会执行未期待的行为,甚至崩溃。
改写函数库
下面强调了将现存函数库改写为可重入和线程安全版本的主要步骤,只适用于C语言的函数库。
*
首先,可重入和线程安全是两个并不等同的概念,一个函数可以是可重入的,也可以是线程安全的,可以两者均满足,可以两者皆不满组(该描述严格的说存在漏洞,参见第二条)。
最后让我们来构想一个线程安全但不可重入的函数: