关于锁--实现原理(一)

通过上次的起源中,我们可以看到锁的必要性。那么为什么会出现两个线程会同时修改那个变量i呢?这个是因为多线程执行的机制造成的。在单核情况下,是怎么实现并发的呢?通常我们知道是靠CPU分片执行实现的,但是不知道是因为中断来实现的CPU分片。我们将i++操作分解开来,可以写成如下汇编的方式(假设存放变量为TMP,寄存器为AX,tmp值为0):

mov ax tmp
add ax 1
mov tmp ax

 
当第一个线程执行完第一个mov操作之后,此时ax的变量为0,然后来了中断请求,cpu将ax的值压入堆栈,然后第二个线程也执行了上述操作,执行完之后,也来了中断请求,第二个线程也将此值(为0)压入堆栈;CPU交给第一个线程执行,首先是恢复线程,变量ax获得弹栈结果0,然后执行add操作,执行完之后再mov回tmp变量,执行完之后响应中断,将CPU交给第二个线程,第二个线程也会进行一遍如上操作,这样就会造成了两个线程同时都加了1,但是变量确是2的情况。

回头再看看这个过程,其实就是一个事情:本来应该再一个完整步骤中进行的“读取—加1—写回”操作被拆成了三个部分来执行了,并且这三个部分还会被穿插进行,导致了数据的不一致。很自然的,解决这个问题的办法,我们就想到了让这个操作放在一起执行,再执行过程中不响应中断,我们一般把这种执行操作特性叫做原子性。这个操作很明显不是一般程序能够控制的,除非是能够控制中断指令,所以这个操作需要底层执行,幸运的时候,现在的底层一般都提供了CAS操作原语来帮我们实现。

CAS指令原语是CPU级别中得,对于多核的情况怎么办呢?在多核中,多个CPU不能互相控制,因此使用CAS指令解决不了我们的问题,这个时候我们需要寻找另一种方式来将其中的一个动作改为串行。于是将实现转向了从内存中读取数据过程—总线。总线的英文名称Bus是很到位的,这个通道是连接内存到各个CPU的,如果我们再总线上设立一个关卡,使得从读取开始“关闭总线”(避免其他CPU也读取此变量值),一直到完成操作写回到内存才释放开,就可以完成了。

总结一下,要实现锁需要满足两个条件:
1、有一个全局的变量
2、对这个全局变量的read-add-write是原子操作。

下节再说说具体的一些语言(如Java)、操作系统、分布式锁的实现。

猜你喜欢

转载自jianfeihit.iteye.com/blog/2028464