32. Common Concurrency Problems
32.1 What Types Of Bugs Exist?
- 并发中有哪些类型的bug?大致分为两种:non-deadlock bugs和deadlock bugs。
32.2 Non-Deadlock Bugs
- 主要又分为两类:atomicity violation和order violation。
- atomicity violation:
可能出现的Bug:
解决办法:
- order violation:
可能出现的bug:
解决方法:
- Non-Deadlock Bugs: Summary
97%的非死锁bug都是原子性和同步的问题,一般通过lock可以解决。
32.3 Deadlock Bugs
- 死锁,当不同进程循环等待资源时,就会造成死锁。
假设线程1占有lock1,线程2占有lock2,那么就造成了死锁。
- 死锁产生的条件:
Mutual exclusion:线程要对想访问的资源申请互斥访问,比如加锁。
Hold and wait:当线程在等待其他资源时,保持自己已经申请到的资源。
No preemption:不能强制线程放弃已占有的资源。
Circular wait:存在一条循环等待链,其中已经占有部分资源的线程在等待其他线程。
四个条件必须同时满足才能产生死锁。也就是说只要破坏其中一个条件就可以避免死锁。下面康康如何破坏这四个条件。
- Prevention
Circular wait:
可以通过固定资源的访问顺序来破坏循环等待条件。比如在上面那个栗子中,可以规定线程必须先申请lock1再申请lock2,这样就可以避免死锁。但是这种做法往往不现实,因为你要知道系统中所有要用到的资源,并且不方便新增资源。
Hold and wait:
可以通过让线程一次性申请完所需的所有资源后再往下进行,像这样:
但是这种做法会降低系统的并发性。
No preemption:
可以通过当线程由于申请不到其他资源而等待时,释放掉其已经占有的资源,像这样:
Mutual exclusion:
可以通过不使用lock的方式来申请对资源的访问,比如使用compareAndSwap:
- Deadlock Avoidance via Scheduling
除了死锁预防,还可以死锁避免。死锁避免需要知道运行过程中的线程可能需要申请哪些lock,并合理安排对这些lock的调度。比如银行家算法,虽然可以有效避免死锁,但是应用场景比较局限,并且会限制系统的并发性。
- Detect and Recover
最后是检测和恢复,也就是说允许系统产生死锁,但是产生死锁后通过一定的手段对死锁进行恢复。比如通过检测进程资源图中是否存在循环等待来判断死锁,或者一个线程等待资源时间过长就判断发生死锁等;然后再通过强制释放某个进程的资源来打破死锁。
32.4 Summary
- 这一章介绍了并发问题可能存在Bug,分为非死锁问题和死锁问题。非死锁问题主要是原子性和顺序性的问题;死锁问题主要是如何预防、避免、检测并恢复。