定义
(1).线程安全函数:一般说来,一个函数被称为线程安全的,当它被多个并发线程反复调用时,它会一直产生正确的结果。
(2).可重入:程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的。
(3).拓展:
1).如果一个函数中用到了全局或静态变量,那么它不是线程安全的,也不是可重入的;
2).如果我们对它加以改进,在访问全局或静态变量时使用互斥量或信号量等方式加锁,则可以使它变成线程安全的,但此时它仍然是不可重入的,因为通常加锁方式是针对不同线程的访问,而对同一线程可能出现问题;
3).如果将函数中的全局或静态变量去掉,改成函数参数等其他形式,则有可能使函数变成既线程安全,又可重入。
联系
可重入函数是线程安全函数的一个真子集。即可重入函数是线程安全函数,但是反过来,线程安全函数未必是可重入函数。
区别
(1)解决问题:
a.可重入函数要解决的问题是,不在函数内部使用静态或全局数据,不返回静态或全局数据,也不调用不可重入函数。
b.线程安全函数要解决的问题是,多个线程调用函数时访问资源冲突。
(2)确保措施
a.确保线程安全的措施是:线程安全函数不使用共享数据(全局、静态或堆)或者对共享数据实施同步机制保护。
b.保障可重入的措施:不共享数据并且不调用不可重入函数。
(1)不要使用static变量和全局变量,坚持只用局部变量;
(2)若必须访问全局变量,利用互斥信号量来保护全局变量;
(3)获取得知哪些系统调用是可重入的,在多任务处理程序中都使用安全的系统调用;
(4)不调用其它任何不可重入的函数;
(5)谨慎使用堆栈malloc/new。
(3)变化
函数如果使用静态变量,通过加锁后可以转成线程安全函数,但仍然有可能不是可重入的,比如 strtok。strtok是既不可重入的,也不是线程安全的。加锁的strtok不是可重入的,但线程安全。而 strtok_r既是可重入的,也是线程安全的。
4. 函数的线程不安全与不可重入的原因
(1)任何线程不安全问题的根源都是“共享数据”。所以,不使用任何共享数据的函数(即:可重入函 数)肯定是线程安全的。
(2)不可重入函数的原因在于:
a. 已知它们使用静态数据结构
b. 它们调用malloc和free.
因为malloc通常会为所分配的存储区维护一个链接表,而插入执行信号处理函数的时候,进程可能正 在修改此链接表。
c. 它们是标准IO函数.
因为标准IO库的很多实现都使用了全局数据结构
(3)线程安全函数不一定是可重入函数,因为即使有线程有共享数据,线程被并发调用的时候也可以使 其结果正确--通过同步操作保证正确性。
共享数据可以是:
函数把返回结果放到一个公共的位置
由调用者传入的线程间共享的指针变量或者引用变量
函数内部本来就会使用的共享静态变量
4. 补充
(1)常见的不可重入函数有:
printf --------引用全局变量stdout
malloc --------全局内存分配表
free --------全局内存分配表
(2)不可重入的解决方举例(printf)
例如,程序正在调用printf输出,但是在调用printf时,出现了信号,对应的信号处理函数也有printf语句,就会导致两个printf的输出混杂在一起。
如果是给printf加锁的话,同样是上面的情况就会导致死锁。对于这种情况,采用的方法一般是在特定的区域屏蔽一定的信号。
屏蔽信号的方法:
1> signal(SIGPIPE, SIG_IGN); //忽略一些信号
2> sigprocmask()
sigprocmask只为单线程定义的
3> pthread_sigmask()
pthread_sigmasks可以在多线程中使用
本文出自 “sunshine225” 博客,请务必保留此出处http://10707460.blog.51cto.com/10697460/1770763
原文地址:http://10707460.blog.51cto.com/10697460/1770763