一、同步介绍
1、临界区与竞争条件
所谓临界区(critical regions)就是访问和操作共享数据的代码段。为了避免在临界区中并发访问,编程者必须保证这些代码原子地执行——也就是说,代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样。如果两个执行线程有可能处于同一个临界区中同时执行,那么就是程序包含一个bug,如果这种情况发生了,我们就称之为竞争条件(race conditions,简称竞态),避免并发和防止竞争条件被称为同步(synchronization)。
在linux中,主要的竞态发生在如下几种情况:
(1)对称多处理器(SMP)多个CPU
特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。
(2)单CPU内进程与抢占它的进程
(3)中断(硬中断、软中断、Tasklet、中断下半部)与进程之间
只要并发的多个执行单元存在对共享资源的访问,竞态就有可能发生。
如果中断处理程序访问进程正在访问的资源,则竞态也会发生。
多个中断之间本身也可能引起并发而导致竞态(中断被更高优先级的中断打断)。
互斥锁,自旋锁,CAS,原子操作详细说明点击:「链接」
2、死锁
死锁的产生需要一定条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了,所有线程都在相互等待,但它们永远不会释放已经占有的资源,于是任何线程都无法继续,这便意味着死锁的发生。
最简单的死锁例子是自死锁:
-
获得锁
-
再次试图获得锁
-
等待锁重新利用
-
......
这种情况属于一个线程一把锁,自己等自己,一般是一个函数等另一个函数,从广义上说就是一种嵌套使用。我曾经的经验总结《踩坑经验总结(四):死锁》就属于这种情况。
最常见的死锁例子是ABBA锁:
-
线程1
-
获得锁A
-
试图获得锁B
-
等待锁B
-
......
-
线程2
-
获得锁B
-
试图获得锁A
-
等待锁A
-
......
这种问题确实很常见,在数据库《MySQL InnoDB技术内幕:内存管理、事务和锁》出现的往往也是这种类型的死锁。
3、加锁规则
预防死锁非常重要,那该注意些什么呢?
(1)按顺序加锁。使用嵌套锁是必须保证以正确的顺序获取锁,这样可以阻止致命的拥抱类死锁,即ABBA锁。最好能记录下锁的顺序,后续都按此顺序使用。
(2)防止发生饥饿。特别是在一些大循环中,尽量将锁移入内部,否则外面等太久。如果发生死循环,就会出现饥饿。
(3)不要重复请求同一把锁。这是针对自死锁的情况,但是一旦出现这种情况,往往不明显,即不是很明显的嵌套,转了几个弯弯,就叫曲线嵌套吧。
(4)设计应力求简单。越复杂的加锁方案越有可能造成死锁。
这里的每一项都很重要,对于应用程序同样适合。再重点说下设计。
在最开始设计代码的时候,就应该考虑加锁;越往后考虑,付出代价越大,效果反而越不理想。那么设计阶段加锁时一定要考虑,为什么要加锁,为了保护什么数据?我认为这是一个定位的问题。需求阶段对一个产品的定位,设计阶段对数据的定位,决定了后续一系列的