全部博文(73)
分类: LINUX
2009-04-23 17:17:30
一个函数被称作“线程安全”的,当且仅当它被多个线程反复调用时,它会一直产生令人期待的正确的结果。反之为“线程不安全”函数,它主要有两种类型:
(1)不保护共享变量的函数,包括全局变量和本地静态变量(static)。
我们可以使用POSIX信号量加以解决:
Sem_t mutex;
Sem_init(&mutex,0,1);
Sem_wait(&mutex);
……
……
Sem_post(&mutex);
优点:简单易行;
缺点:同步操作p、v将影响程序的执行时间。
(2)返回指向静态变量的指针的函数,如gethostbyname,gethostbyaddr,inet_ntoa等等。
我一般这样使用gethodtbyname:
Struct hostent *hostp;
Hostp=gethostbyname(scorpion.cublog.cn);
……
……
Gethostbyname将执行结果存放在自己的static
struct hostent变量中,并返回一个指向这个结构的指针。如果我们从并发线程中调用这个函数,那么将发生不可预料的结果,正在被一个线程使用的结果,可能悄悄地被其它线程覆盖。
解决办法:(a)、改写库函数gethostbyname,增加一个参数,将本地结构变量传入gethostbyname,
如:
Struct
hostent host;
Gethostbyname(&host,scorpion.cublog.cn);
再使用host方可!
(b)、改写库函数gethostbyname,不是返回指针,而是直接返回struct hostent.
缺点:struct hostent结构较大,影响函数的效率。
以上两种办法(a)、(b),要求修改库函数,基本上不可行!
(c)、使用lock –and-copy技术,即定义自己的封装函数,通过调用它来取代直接使用gethostbyname.
struct hostent *gethostbyname_my(char *hostname)
{
struct hostent
*shar,*unshar;
unshar=malloc(sizeof(struct
hostent))
sem_wait(&mutex);
shar=gethosybyname(hostname);
*unshar=*shar;
srm_post(&mutex);
return unshar;
}
现在大多数unix\linux操作系统,提供了线程不安全函数的可重入版本,如:gethostbyname_r。但是不同的系统可能接口不一样,所以建议不使用它们!还是使用我们自己的封装函数。
可重入函数,是线程安全函数的一种。特点:当它们被多个线程调用时,不会引用任何共享数据,也就是不引用静态或全局变量。如,gethostbyname_my是线程安全函数,而不是可重入函数。
线程安全 和 可重入 其实是2个东西,
不过一般情况下可以认为
可重入函数都是
线程安全的
也有其他 情况,这个要看定义了,比如
void foo(int *i)
{
int b = *i;
b++;
*i = b;
};
这个函数不是线程安全的,但是可重入
如果一个函数使用线程本地存储,互斥体等,也可以实现线程安全,但是可以不是 可重入的
这两个不是一个意思。但有时候有重叠。在多线程情况下,县城安全是可重入的必要条件。但是线程安全不一定能保证可重入。
比如单线程程序的函数,当然不存在线程安全问题,但不一定就是可重入的。比较经典的例子是最早DOS的TSR程序,由于用了同一堆栈指针,函数重入的时候会把栈写坏。还有比如Fortran一类的不支持递归的语言,由于变量是静态地址,无法可重入。
总之对于可重入函数,里面不能有大家同时可以用的公共资源。比如下面程序:
int 阶乘(int
n)
{
static int i = 1;
if (n)
i = i * 阶乘(n-1);
else
i = 0;
return i;
}
即使单线程也会出问题。
Chiyer(星羽) 兄给出了全局变量的线程安全处理示例:这种情况说明的是多个线程实例并发操作同一存储区域的数据的问题,就应用角度来说,本身就是不可重入的。
可重入的定义请翻阅相关课本。
微软早期的OS中的多线程是不可重入的,比如下面这个线程函数并发 3
次
void fun(void*p)
{
int i = 0;
++i;
}
最后得到的结果可能是
1,2,3 中任何结果。可能当时微软的多线程函数中的局部变量不是采用的堆栈,而是采用的共享存储。当时要做到线程函数中的局部变量相互独立(即保证线程安全,也可
以说保证线程函数可重入----但可重入表现在计算科学各方面,不只是线程),Borland
提供了 C++
多线程库。
说一个不可重入,但是线程安全的例子,帮助大家理解
funca()
{
lock(a);
......//interrupt by signal and call funca(), so dead lock
unlock(a);
}
int main()
{
signal(sig, funca);
funca();
}