关于线程安全与可重入函数

一、线程安全

一个函数被称为线程安全的,当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。如果一个函数不是线程安全的,我们就说它是线程不安全的。

四个(不相交的)线程不安全函数类:

1.不保护共享变量的函数

将这类线程不安全的函数变成线程安全的,相对比较容易:利用像P和V操作这样的同步操作来保护共享的变量。这个方法的优点是在调用程序中不需要做任何的修改;缺点是同步操作将减慢程序的执行时间。

2.保持跨越多个调用的状态的函数

一个伪随机数生成器是这类线程不安全函数的简单例子。rand函数是线程不安全的,因为当前调用的结果依赖于前次调用的中间结果,当调用srand为rand设置了一个种子后,我们从一个单线程中反复地调用rand,能够预期得到一个可重复的随机数字序列。然而,如果多线程调用rand函数,这个假设就不再成立了。

使得像rand这样的函数线程安全的唯一方法是重写它,使得它不再使用任何static数据,而是依靠调用者在参数中传递状态信息。这样做的缺点是程序员还要被迫修改调用程序中的代码。在一个大的程序中,可能有成百上千个不同的调用位置,做这样的修改将是非常麻烦的,而且容易出错。

3.返回指向静态变量的指针的函数

某些函数,例如ctime和gethostbyname,将计算结果放在一个static变量里,然后返回一个指向这个变量的指针。如果从并发线程中调用这些函数,那么将可能发生灾难,因为正在被一个线程使用的结果会被另一个线程悄悄地覆盖了。

有两种方法来处理这类线程不安全函数。一种选择是重写函数,使得调用者传递存放结果的变量的地址。这就消除了所有共享数据,但是它要求程序员能够修改函数的源代码。

如果线程不安全函数是难以修改或不可能修改的(例如代码非常复杂或是没有源代码可用),那么另外一种选择就是使用加锁-拷贝(lock-and-copy)技术。基本思想是将线程不安全函数与互斥锁联系起来。在每一个调用位置,对互斥锁加锁,调用线程不安全函数,将函数返回的结果拷贝到一个私有的存储器位置,然后对互斥锁解锁。

4.调用线程不安全函数的函数

如果函数f调用线程不安全函数g,那么f就是线程不安全的吗?不一定,如果g是第2类函数,即依赖于跨越多次调用的状态,那么f也是线程不安全的,而且除了重写g以外,没有什么办法。然而,如果g是第1类或者第3类函数,那么只要用一个互斥锁保护调用位置和任何得到的共享数据,f仍然可能是线程安全的。


二、可重入性

可重入函数:是一类重要的线程安全函数,其特点在于它们具有这样一种属性,当它们被多个线程调用时,不会引入任何共享数据。

尽管线程安全与可重入有时会被用作同义词,但它们并不完全等价。

可重入函数集合是线程安全函数的一个真子集。


可重入函数通常要比不可重入的线程安全的函数高效一些,因为它们不需要同步操作。将第2类线程不安全函数转化为线程安全函数的唯一方法就是重写它,使之变为可重入的。

例:rand函数的可重入版本(关键思想是用一个调用者传递进来的指针取代了静态变量):

#include <stdio.h>
#include <unistd.h>

int rand_r(unsigned int *nextp)
{
	*nextp = *nextp * 1103515245 + 12345;
	return (unsigned int)(*nextp / 65536) % 32768;
}

int main()
{
	unsigned int i = 11;
	unsigned int *nextp = &i;
	while(1)
	{
		sleep(1);
		unsigned int ret = rand_r(nextp);
		printf("%ud\n", ret);
	}

	return 0;
}

如果所有的函数参数都是传值传递的(即没有指针),并且所有的数据引用都是本地的自动栈变量(即没有引用静态或全局变量),那么函数就是显示可重入的。也就是说无论它是如何被调用的,我们都可以断言它是可重入的。

然而,如果允许显示可重入函数的一些参数是引用传递的(即允许它们传递指针),那么我们就得到了一个隐式可重入的函数,也就是说,如果调用线程小心地传递指向非共享数据的指针,那么它是可重入的。

大多数Unix函数,包括定义在标准C库中的函数(例如malloc,free,printf,scanf)都是线程安全的,只有一小部分例外:


如果我们需要在一个线程化的程序中调用这些函数,对调用者来说最不惹麻烦的方法是加锁-拷贝。然而,加锁-拷贝方法有许多缺点。首先,额外的同步降低了程序的速度;其次,像gethostbyname这样的函数返回指向复杂结构的指针,要拷贝整个结构层次,需要深层拷贝结构。且加锁-拷贝这样的方式对第2类线程不安全函数并不有效。

因此,Unix系统提供大多数线程不安全函数的可重入版本,尽可能地应该使用这些函数。



猜你喜欢

转载自blog.csdn.net/if9600/article/details/73188196