一、线程安全
1.线程安全的基本概念
当多个线程访问某个方法或者变量的时候,不管你通过怎么样的调用方法或者说这些线程是如何交替的运行的,每个线程的执行结果都是我们设想的正确行为,那么我们就可以说这个方法或者变量是线程安全的。
2.线程不安全场景
多线程访问全局变量,由于CPU调度次序不同,线程交替运行,可能某一时刻同时读取到同一个值,然后互相写count,数据污染,导致最终结果不符合预期(20000)
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
int count = 0;
void *task()
{
int i = 0;
for (; i < 10000; i++) {
count++;
printf("count1:%d\n", count);
}
}
int main()
{
pthread_t a;
pthread_t b;
pthread_create(&a, NULL, task, NULL);
pthread_create(&b, NULL, task, NULL);
pthread_join(a, NULL);
pthread_join(b, NULL);
return 0;
}
3.实现线程安全的常用方法
- (1)原子性操作(单指令操作)
- (2)同步与锁,保证两个线程在临界区公共资源处串行执行
- (3)将任务函数本身处理成为线程安全函数(比如不使用全局变量、静态变量)
二、信号安全
1.信号安全概念
一般的信号安全指的是在信号处理中,发生信号异常时调用信号处理函数,都不会造成除了信号处理函数以外的其他功能变化。可重入函数就满足了信号安全的特征,所以通常不可重入函数不是信号安全的,就像malloc/free内部有维护全局的共享内存链表,且加锁了,是不可重入函数,所以malloc/free是信号非安全函数。
2.信号不安全
若此时一个线程中正在调用malloc/free,且刚好在malloc函数加锁之后,被异常信号中断,进入信号处理函数,若在信号处理函数中调用了malloc/free等信号非安全函数,就出现了对同一线程对heap lock连续加了两次锁,则会出现死锁(死锁原因:a线程在信号处理函数中一直在等待主程序解锁,产生阻塞,在信号处理函数中回不到正常程序中,与常见的两个线程产生死锁的理解不同),导致异常。由于要刚好构造在malloc加锁之后再次调用malloc的情况,需要开启一个线程循环对另一个循环调用了malloc的线程发送异常信号,这个线程收到了异常信号后,就会进入信号处理函数,能复现出死锁的场景。
碰到了在malloc加锁之后再次调用malloc的情况,出现死锁,程序卡死
3.怎么实现信号安全
(1)在信号处理函数中不要使用信号非安全函数,因为这种情况虽然属于小概率事件,但一出现就会导致程序异常,甚至直接重启复位,而且难以定位
(2)确认函数是否是信号安全函数查看| MT-Safe | AS-Safe | AC-Safe | 这三个特性
三、可重入函数
1.可重入概念
一个函数被重入,表示这个函数没有执行完成,但由于外部因素或内部因素,又一次进入该函数执行。一个函数称为可重入的,表明该函数被重入之后不会产生任何不良后果。
2.可重入最常见场景
可重入函数指的是这个函数在执行的过程中被中断,程序的执行流跑到另外一个地方把这个函数重新执行了一遍,执行完成后返回到被中断的地方再次运行之前的函数,而整个过程不会影响这个被打断的函数的最终结果,如下图:
3.不可重入特征
- 1)使用或返回了静态数据结构(全局变量或静态变量),
- 2)标准I/O程序库的一部分(内部会有全局锁,锁也是一种状态)
- 3)调用了一个不可重入的函数,例如调用malloc或free (malloc内部维护了全局的链表用来管理分配的内存,这就是状态信息,free也一样),而且malloc函数虽然本身是线程安全的,但系统调用时存在全局的heap lock(堆内存锁(全局锁),保证堆内存分配时的一致连续性),信号句柄中使用malloc函数时,有可能会发生在主程序调用malloc时,但还未结束,heap lock还未释放,被信号所中断,再次调用malloc函数,就会出现同一线程对heap lock连续两次锁,即会出现死锁 deadlock。