【C】线程安全、信号安全与可重入函数的区别

2,550 阅读5分钟

一、线程安全

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 | 这三个特性

www.gnu.org/software/li…

www.gnu.org/software/li…

三、可重入函数

1.可重入概念

一个函数被重入,表示这个函数没有执行完成,但由于外部因素或内部因素,又一次进入该函数执行。一个函数称为可重入的,表明该函数被重入之后不会产生任何不良后果。

2.可重入最常见场景

可重入函数指的是这个函数在执行的过程中被中断,程序的执行流跑到另外一个地方把这个函数重新执行了一遍,执行完成后返回到被中断的地方再次运行之前的函数,而整个过程不会影响这个被打断的函数的最终结果,如下图:

3.不可重入特征

  • 1)使用或返回了静态数据结构(全局变量或静态变量),
  • 2)标准I/O程序库的一部分(内部会有全局锁,锁也是一种状态)
  • 3)调用了一个不可重入的函数,例如调用malloc或free (malloc内部维护了全局的链表用来管理分配的内存,这就是状态信息,free也一样),而且malloc函数虽然本身是线程安全的,但系统调用时存在全局的heap lock(堆内存锁(全局锁),保证堆内存分配时的一致连续性),信号句柄中使用malloc函数时,有可能会发生在主程序调用malloc时,但还未结束,heap lock还未释放,被信号所中断,再次调用malloc函数,就会出现同一线程对heap lock连续两次锁,即会出现死锁 deadlock。

四、线程安全、信号安全、可重入函数三者的区别与联系

1.一个函数可以是线程安全但不是可重入的,例如一个函数通过mutex来保护了某些共享资源,从线程安全的角度来看,这个函数是线程安全的,但是因为使用了mutex,使得函数是有状态的,因此这个函数不是可重入的。

2.但可重入函数肯定是线程安全的也是信号安全的

3.线程安全的并不一定是信号安全的