1、 线程安全:
线程安全是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程访问完,其他线程才可以使用。不会出现数据不一致或数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
四类线程不安全函数:
(1)不保护共享变量的函数(线程不安全):将这类函数转化为线程安全的,相对比较容易:可利用像P和V操作这样的同步操作来保护共享变量。优点在于调用程序中不需要做任何修改,缺点是同步操作将减慢程序的运行时间。
(2)保持跨越多个调用的状态函数(线程不安全):一个伪随机数生成器就是这类的不安全函数的简单例子。
usigned int next = 1;
int rand(){
next= next * 1103515245 + 12345;
return(usigned int)(next/65536)%32768;
}
void srand(usigned int seed){
next= seed;
}
rand 函数是线程不安全的,因为当前调用的结果依赖于前次调用的中间结果。当我们调用srand为rand设置一个种子之后,我们反复从一个种子中调用rand,我们能够预期一个 可重复的随机数字序列。但如果多个线程同时调用rand函数,那假设就不成立。使用rand函数变为线程安全的唯一方式是重写它,使得它不再使用任何静态数据,取而代之的依靠调用者在参数中传递状态信息。缺点要被迫改写调用程序的代码。
(3)返回指向静态变量指针的函数:
某些函数(如gethostbyname)将计算结果放在静态结构中,并返回一个指向这个结构的指针。如果我们从并发线程中调用这些函数,那么将可能发生灾难,因为正在被一个线程使用的结果会被另一个线程悄悄地覆盖了。
有两种方法来处理这类线程不安全函数。一种是选择重写函数,使得调用者传递存放结果的结构地址。这就消除了所有共享数据,但是它要求程序员还要改写调用者的代码。
如果线程不安全函数是难以修改或不可修改的(例如,它是从一个库中链接过来的),那么另外一种选择就是使用lock-and-copy(加锁-拷贝)技术。这个概念将线程不安全函数与互斥锁联系起来。在每个调用位置,对互斥锁加锁,调用函数不安全函数,动态地为结果非配存储器,拷贝函数返回的结果到这个存储器位置,然后对互斥锁解锁。一个吸引人的变化是定义了一个线程安全的封装(wrapper)函数,它执行lock-and-copy,然后调用这个封转函数来取代所有线程不安全的函数。例如下面的gethostbyname的线程安全函数。
struct hostent* gethostbyname_ts(char* host){
struct hostent* shared,* unsharedp;
unsharedp =Malloc(sizeof(struct hostent));
P(&mutex)
Hared =gethostbyname(hostname);*unsharedp =* shared;
V(&mutex);returnunsharedp;
}
(4)调用线程不安全函数的函数
如果函数f调用线程不安全函数g,那么f就是线程不安全的吗?不一定。如果g是类2类函数,即依赖于跨越多次调用的状态,那么f也是不安全的,而且除了重写g以外,没有什么办法。然而如果g是第1类或者第3类函数,那么只要用互斥锁保护调用位置和任何得到的共享数据,f可能仍然是线程安全的。比如上面的gethostbyname_ts。
2、 可重入函数:
函数被不同的控制流程调⽤用,有可能在第一次调用还没返回时就再次进入该函 数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant)函数。
•可重入特点
由于可重入函数多次调用不会出错,因此可重入函数不用担心数据会被破坏。可重入函数任何时候都可以被中断,一段时间后又可以运行,而相应的数据不会丢失。可重入函数只使用局部变量,即保存在CPU寄存器或者堆栈中;或者如果使用全局变量时,则要对全局变量予以保护。
•不可重入特点
如果一个函数符合以下条件之一的,则是不可重入的:
(1)调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
(2)调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
(3)可重入体内使用了静态的数据结构。
3、 可重入函数与线程安全的关系:
可重入函数与线程安全的区别与联系:
(1)线程安全是在多个线程情况下引发的,而可重入函数可以在只有一个线程的情况下来说。
(2)线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
(3)如果一个函数中有全局或静态变量,那么这个函数既不是线程安全也不是可重入的。如果我们对它加以改进,在访问全局或静态变量时使用互斥量或信号量等方式加锁,则可以使它变成线程安全的,但此时它仍然是不可重入的,如果将函数中的全局或静态变量去掉,改成函数参数等其他形式,则有可能使函数变成既线程安全,又可重入。
(4)如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。
(5)线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。
(6)strtok函数是既不可重入的,也不是线程安全的;加锁的strtok不是可重入的,但线程安全;而strtok_r既是可重入的,也是线程安全的。
注:
1.可重入概念只和函数访问的变量类型有关,和是否使用锁没有关系。
2.而线程安全和锁的使用关系密切,很多时候线程安全是靠锁来保证的