线程安全
所谓线程安全,就是当多个线程访问同一个数据时,不会造成数据出错。其实它是采用了加锁的机制来保证在一个线程访问该数据时,其他的线程不可以访问,直到等到那个线程访问结束才可以访问。而线程不安全,就是没有采用加锁的机制来保证这一点。测试代码
#include<stdio.h>
#include<pthread.h>
int g_count = 0;
void* threadCount(void* arg)
{
int tmp = 0;
int count = 0;
while(count < 5000)
{
tmp = g_count;
printf("线程ID : %u , g_count : %d\n",pthread_self(),g_count);//打印线程自己的ID,以及当前的g_count值
g_count = tmp+1;
count ++;
}
}
int main()
{
pthread_t thread1,thread2;//声明线程变量
pthread_create(&thread1, NULL , threadCount,NULL);//创建线程
pthread_create(&thread2, NULL , threadCount,NULL);
pthread_join(thread1,NULL);//以阻塞的方式等待线程的终止
pthread_join(thread2,NULL);//以阻塞的方式等待线程的终止
return 0;
}
运行结果
第一次运行结果
第二次运行结果
我们发现每次的运行结果都不大一样
分析
造成线程安全的程序都是由于全局变量以及静态变量引起的
解决方法
利用互斥锁来实现互斥
四类不是线程安全的函数
(1)不保护共享变量的函数。
(2)函数状态随着调用而改变的函数
(3)返回静态指针变量的函数
(4)调用非线程安全的函数
可重入函数
概念
重入
当函数被不同的控制流程调用时,有可能在一次调用没有返回时,另一个控制流程又重新进入该函数,这种现象称之为重入
不可重入函数
当发生重入现象后,程序逻辑有可能会发生错乱。这类函数成为不可重入函数。
可重入函数
当发生重入现象后,程序逻辑不会错乱。
哪些函数是一定不可重入
1. 调用malloc或free,因为malloc也是用全局链表来管理堆的。
2. 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的⽅式使用全局数据结构。
3. SUS规定有些系统函数必须以线程安全的方式实现。
测试代码
#include<stdio.h>
int flag = 0;
void myhandler(int signal)
{
flag = 1;
printf("falg = %d\n",flag);
}
int main()
{
signal(2,myhandler);
printf("proc start!\n");
while(!flag);
printf("proc end!\n");
return 0;
}
运行结果
接下来我们使用 -O3选项来让编译器进行优化
优化后的运行结果如下
进过编译器优化后,全局变量的flag保存在寄存器中
而myhandler函数中的flag保存在内存中,此时发送信号改变的是内存中的flag值而寄存器中的flag仍为0,所以程序仍会运行。
现在我们加入volatile 关键字
运行结果如下
程序又正常运行了
线程安全和可重入函数的区别
1.线程安全是在多线程情况下发生,可重入函数在只有一个线程的情况下发生。
2.线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
3.一个函数有全局变量,则这个函数既不是线程安全也不是可重入的;一个函数当中的数据全身自身栈空间的,则这个函数即使线程安全也是可重入的。
4.对临界资源的访问加锁,则函数是线程安全的;但如果重入函数的话加锁还未释放,则会产生死锁,因此不能重入。
5.线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作不影响结果,使结果是相同且正确。