一、线程安全

    线程是操作系统调度的最小单元,进程是分配资源的最小单元。一个进程可以派生出多个线程,这些线程独立执行,共享进程资源。多线程大大提高了系统的运行速度,但是也引出了一些问题,线程主要由控制流程和资源使用两部分构成,所以就面临一个问题就是对共享资源访问的问题。为了确保资源的正确使用,我们在编写程序时就需要考虑避免竞争条件和死锁,需要更多的考虑使用线程互斥变量。

线程安全的函数就是一个在代码层面上解决这个线程安全问题比较好的方法,在多线程并发执行的情况下,一个函数可以安全的被多个线程并发调用,就可以说这个函数是线程安全的。

 

通过以下几条确定一个函数是线程不安全的:

(1)函数中访问了全局变量和堆;

(2)函数中重新分配和释放全局资源;

(3)函数中通过句柄和指针的不直接访问;

(4)函数中使用了其它线程不安全的函数或者变量。

 

编写安全函数要注意以下几点:

(1)减少对临界资源的依赖,避免访问全局变量、静态变量或者其它共享资源(如果必须使用,一定要用互斥所保护);

(2)调用的函数要保证是安全的(如果调用的函数不是线程安全的,一定要用互斥锁保护)。

 

二、可重入函数(显示和隐式)

    可重入函数,即当一个函数被一个线程调用时,可以允许被其他线程再调用。

比如说:程序在执行到某个函数foo()时,收到信号,于是暂停了目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰好也进入到刚才执行的函数foo(),这样便发生了重入,此时如果foo()能够正确的运行,而处理完成后,之前暂停的foo()也能够正确运行,则说明这个函数是重入的。

    我们可以了解,如果一个函数是可重入的,那么它肯定是线程安全的,但是反过来说是不对的,也就是说可重入函数是安全函数的子集。

比如说:函数func()在执行过程中需要访问某个公共资源,因此为了实现线程安全,在使用该资源前加锁,在不需要资源解锁,该函数在某次执行过程中,在已经获得资源锁之后,有异步信号发生,程序的执行流转交给对应的信号处理函数,而在该信号处理函数中也要调用该函数func(),那么func()在这次执行中仍会在访问共享资源前试图获得资源锁,但是刚才那个已经获得了资源锁,因此信号处理函数阻塞,信号处理函数结束前被信号中断的线程无法恢复执行,也就没有释放锁的机会,这样就出现了线程和信号处理函数之间的死锁局面。尽管func()通过加锁的方式保证线程安全,但是由于函数体对共享资源的访问,导致func()不是可重入函数。

 

一个函数要想成为可重入函数,必须满足以下几点:

(1)不能使用静态或者全局的非常量数据;

(2)不能够返回地址给静态或者全局的非常量的数据;

(3)函数使用的数据有调用者提供;

(4)不能够依赖单一资源的锁;

(5)不能够调用非可重入的函数。