常见锁的概念
死锁
什么叫死锁呢?
- 死锁是指一组进程或线程均占有不会释放的资源,但因互相申请被其他进程所占有的不会释放的资源而处于的一种永久等待的状态
换句话说就是,死锁就是在多执行流的情况下,多个执行流各自占有对应的锁,但不释放锁,同时多个执行流还会申请其他执行流锁持有的锁,而导致多执行流进入到一种永久阻塞的状态,这种状态就叫死锁
这样讲的话不方便大家理解,我们来举一个例子:现在有两个人,张三和李四,他们各自拥有五块钱,但是他们都想买一个订书机,但是这个订书机需要十块钱,此时张三对李四说,把你的五块钱给我,我买了这个订书机之后给你用;而李四接着说,为什么是我把我的五块钱给你,为什么不能是你把五块钱给我,让我买这个订书机呢?于是他们两个就互相僵持着,谁都没有办法买下这个订书机,这种状态我们就叫做死锁
那么上面我们所说的都是两把锁的情况,那么一把锁的情况下会产生死锁呢?
答案是可能的,假设我们对一把锁同时申请两次,那么就会产生死锁,但这种情况一般情况下不会存在,所以我们在这里不做过多的讨论
死锁产生的条件
那么我们该如何避免死锁呢?为了避免死锁的问题,我们需要来了解一下死锁产生的必要条件
- 互斥条件:一个资源每次只能被一个执行流使用
- 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源不进行释放,这里涉及到申请资源和保持资源的情况,并且这两个动作必须同时发申,所以我们叫作请求与保持条件
- 不剥夺条件:一个执行流已获得的资源,在未使用前,不能被其他执行流剥夺,这种情况我们称之为不剥夺条件
- 循环等待条件:若干执行流之间形成了一种头尾相接的循环等待资源的关系,换句话说就是,线程A和线程B互相申请对方已经持有的锁,就叫做循环等待条件
以上就是死锁产生的必要条件,也就是说,要想产生死锁的情况,必须同时满足上面我们所讲的四个条件,否则死锁就无法发生,所以为了避免死锁的出现,我们只需要破坏其中一个或多个条件即可
避免死锁
- 对于条件一,我们只需不申请锁,就可以避免互斥条件的发生
- 对于条件二,如果我们申请锁失败,只需要把自己的锁进行释放,让其他的执行流能够拿到我所持有的锁
- 对于条件三:如果我们在线程A在申请线程B的锁失败的条件下,允许线程A强行拿到线程B所持有的锁,此时我们就破坏了不剥夺条件,这里我们需要和保持进行区分,保持是自己保持,剥夺是别人来剥夺
- 对于条件四:尽量保持加锁的顺序一致我们就可以避免循环等待,但是真实情况却远远比这要复杂
STL中的容器是否线程安全
答案是否定的,STL设计的初衷是为了把性能挖掘到极致,而一旦涉及到加锁保证线程安全,就会对性能造成巨大的影响;其次对于不同的容器,加锁的方式不同,性能也可能不同,因此STL默认不是线程安全的,如果要在多线程环境下使用,往往需要调用者自行保证线程安全
智能指针是否线程安全
对于unique_ptr由于只在当前代码块范围内生效,因此不涉及线程安全的问题
对于shared_ptr多个对象需要共用一个引用计数变量,所以会存在线程安全的问题,但是标准库设计的时候考虑到了这个问题,通过基于原子操作(CAS)的方式保证shared_ptr能够高效的原子的操作引用计数