一、什么是线程安全
说到线程安全许多初学者可能会感觉很高大上(说的就是我自己),但是当你试着去搞懂它的时候就不会这么感觉了,其实我们早就接触到了线程安全的问题,只不过自己不知道而已。那么到底什么才是线程安全呢?
线程安全:
代码所在的进程有多个线程在同时运行,这些线程可能在同时运行这些代码,如果多线程下运行的结果和单线程运行的结果是一样的,那么线程就是安全的。反之,线程就是不安全的。
一般情况下,我们定义四类线程不安全的函数:
1、不保护共享变量的函数
2、函数状态随调用改变的函数
3、返回指向静态变量指针的函数
4、调用线程不安全函数的函数
其实在我们学习锁的时候,我们给临界资源加锁也就是一种对于线程安全的考虑。一个进程中的多个线程共享 地址空间和共享资源(static变量、全局变量)如果不加锁的话,就会造成线程不安全的问题。局部变量是不会造成线程安全的,因为线程之间有自己的私有栈和上下文信息,所以只有全局意义上的函数才可能会造成线程安全的问题。
下面我们来看代码:
#include<stdio.h>
#include<pthread.h>
int count = 0;
void* fun(void* arg)
{
int i = 0;
while(i < 5000)
{
i++;
int tmp = count;
printf("count : %d\n",count);
count = tmp+1;
}
}
int main()
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fun,NULL);
pthread_create(&tid2,NULL,fun,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf("count is :%d\n",count);
return 0;
}
第一次运行:
第二次运行:
第二次运行:
我们可以看见三次运行同一个代码的结果完全不一样,这就说明多线程下全局变量的操作会造成数据不一致问题。
二、什么是可重入函数
这里先介绍几个概念:
重入:
即重复调用,函数被不同的流调用,有可能会出现第一次调用还没返回时就再次进入该函数开始下一次调用。
可重入:
当程序被多个线程反复执行,产生的结果正确。
如果一个函数只访问自己的局部变量或参数,称为可重入函数。
不可重入函数:
当程序被多个线程反复调用,产生的结果出错。
当函数访问一个全局的变量或者参数时,有可能因为重入而造成混乱,像这样的函数称为不可重入函数。
可重入的特点:
由于可重入函数多次调用不会出错,因此可重入函数不用担心数据会被破坏。可重入函数任何时候都可以被中断,一段时间后又可以运行,而相应的数据不会丢失。可重入函数只使用局部变量,即保存在CPU寄存器或者堆栈中;或者如果使用全局变量时,则要对全局变量予以保护。
不可重入的特点:
1、调用了malloc/free函数,因为malloc函数是全局链表来管理堆的。
2、调用了标准I/O库函数,标准I/O库函的许多实现都以不可重入的方式使用全局数据结构。
3、可重入体内是使用了静态的数据结构。
可重入函数满足的条件:(编写可重入函数的规范)
1、不使用全局变量和静态变量
2、不使用mallo/free函数
3、不在可重入函数内部调用不可重入函数
4、不返回静态或全局数据,所有数据都由函数调用者提供
5、使用本地数据(全局数据的本地拷贝)来保护全局数据。
6、如果必须访问全局变量,利用互斥机制来保护全局变量
7、不调用了标准I/O函数。标准I/O函数很多都以不可重入的方式实现全局数据结构。
三、线程安全和可重入的关系和区别
联系:
线程安全和重入的区别:
(1)可重入函数是线程安全函数的一种,其特点在于它们被多个线程调用时,不会引用任何共享数据。
(2)线程安全是在多个线程情况下引发的,而可重入函数可以在只有一个线程的情况下来说。
(3)线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
(4)如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。
(5)如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。
(6)线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。