前言:
上文我们实现了一个简单线程池并且还设计了线程池的单例模式【Linux实战 】Linux 线程池的设计、实现与单例模式应用-CSDN博客
本文我们来看看,关于线程安全与死锁的问题。
线程安全与重入问题
概念
**线程安全:**就是多个线程在访问共享资源时,能够正确的执行,不会相互干扰或破坏彼此的执行结果。一般情况下,多个线程并发访问一段只有局部变量的代码时,不会出现不同的结果。但是对全局变量或静态变量而言,在没有锁保护的前提下,很容易出现问题。
**重入:**同一个函数被不同的执行流调用,当一个执行流还没有执行完这个函数,另一个执行流就开始执行了这个函数,我们称之为重入,既一个函数多个执行流同时调用。当重入后函数的运行结果不会出现问题,我们称做可重入函数。反之,为不可重入函数。
常见线程不安全情况:
不保护共享变量的函数
函数状态随着被调用,状态发生变化的函数
返回指向静态变量指针的函数
调用线程不安全函数的函数
常见不可重入的情况:
调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
调用了标准IO库函数,标准IO库的函数很多都是以不可重入的方式设计的
在可重入函数中使用了静态的数据结构
总结
函数是可重入的,那就是线程安全的!而线程是安全的,其函数不一定是可重入的。
思考
STL的容器是否线程安全?
**不安全。**因为STL的设计初衷就是堆性能极致的开发,而一旦涉及到加锁保证安全的环节,其性能就会大打折扣,所以STL是默认是不安全的。在多线程的环境下需要我们自己来保证安全。
智能指针是否安全?
安全。
对于unique_ptr,由于只在当前的代码块中生效,所以不涉及到线程安全问题。
对于shared_ptr,多个对象需要共用一个引用计数,所以会存在线程安全的问题。但是标准库在实现时考虑到了这个问题,让share_ptr的引用计数采用原子性的计数方式,解决了这一问题。
死锁问题
死锁:指在一组进程中的各个进程均占用不会释放的资源,但因互相申请被其他进程不会释放的资源而处于一种永久等待的状态。

申请一把锁是原子的,但申请两把锁是就不一定了。

当线程之间互相申请对方持有的锁,但不释放自己的锁时,就会造成死锁问题。
线程A申请线程B时,B一直不释放,A将会一直进行等待。同样的线程B申请线程A,A一直不释放,B也会一直进行等待。于是线程AB都陷入了一直的等待中。

死锁四个必要条件
互斥条件: 一个资源每次只能被一个执行流使用。
请求与保持: 一个执行流因请求而阻塞时,对已获得的资源保持不放。

不剥夺: 一个执行流获得资源,在未使用完之前,不得被强行剥夺。

循环等待: 若没有申请到资源则进行等待,若干执行流之间形成了头尾相连的循环等待的关系。

避免死锁
想要避免死锁很简单。只需要破坏四个必要条件中的一个即可!