1、自旋锁
自旋锁(Spin Lock)是一种典型的对临界资源进行互斥访问的手段,为了获得一个自旋锁,在某CPU上运行的代码需要先执行一个原子操作,该操作测试并设置(Test And Set)某个内存变量。由于它是原子操作,所以在该操作完成之前其他执行单元不能访问这个内存变量。如果测试表明锁已经空闲,则程序获得这个自旋锁继续执行,否则程序就进行重试获得锁。
理解自旋锁的最简单方式就是将其当成一个变量看待。如果A执行单元首次进入例程,它将持有自旋锁,当B执行单元尝试进入同一个例程的时候,将获知自旋锁已被持有,需要等到A执行单元释放才行。
自旋锁主要是针对SMP架构或单CPU但是可抢占式调度的情况,对于单CPU但是不支持抢占式的系统,自旋锁退化为空操作。
自旋锁实际上是忙等待,当锁不可用时,CPU一直循环执行“Test And Set”该锁直到可以获得该锁,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。因此只有在占用锁时间极少的情况下,使用自旋锁才比较合适。当临界区很大或者临界区要执行的内容要花费很长时间,使用自旋锁会降低系统的性能。
自旋锁可能会导致死锁。引发这个情况最常见的问题就是递归使用一个自旋锁,如果一个已经拥有自旋锁的CPU想第二次获得该自旋锁,该CPU将死锁。
读写自旋锁
实际上对共享资源并发访问时,多个执行单元同时读取是不会有问题的,自旋锁的衍生锁,读写锁可运行读的并发。
读写自旋锁是一种比自旋锁粒度更小的锁机制,但是保留了自旋的概念。但是在“写”方面,最多只能有一个写进程,在“读”方面,可以同时多个读进程。当然读和写也是互斥的。
顺序锁
顺序锁是对读写锁的一种优化,若使用顺序锁,读执行单元不会被写执行单元所阻塞,也就是说,读执行单元在写执行单元被顺序锁保护的共享资源进行写操作的时候仍可以继续读,而不必等待写执行单元完成写操作,写执行单元也不必等待所有读执行单元完成读操作才去进行写操作,但是写写操作仍然是互斥的。
对于顺序锁而言,尽管读写之间不互斥,但是如果读执行单元哎读操作期间,写执行单元已经发生了写操作,那么读执行单元就必须重新读取数据,以保障数据的完整性。
读-复制-更新
RCU(Read-Copy-Update),不同于自旋锁,使用RCU的读端没有锁、内存屏障、原子指令类的开销,几乎可以认为是直接读。而RCU的写执行单元在写的时候会Copy一份共享资源数据副本,然后对副本进行修改,最后使用一个回调机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据的CPU都退出对共享数据读操作的时候。感觉Java中的CopyOnWriteList就是参考这个实现的,只不过Java中的是在更新后立即修改引用。
2、信号量
信号量(Semaphore)是OS中最常见的用于同步和互斥的手段,信号量的值可以使0,1或者N,信号量与OS中的PV操作相对应。
P(S):
将信号量S的值减1,即S-=1
如果S>=0,则该进程继续执行,否则该进程置为等待状态,进入等待队列。
V(S):
将信号量S的值加1,即S+=1
如果S>0,则将唤醒队列中等待信号量的进程。