PostgreSQL数据库锁机制——自旋锁浅析( 二 )


大明看着小明洋洋自得的写的这段代码,鄙视到:“你觉得这样就能实现一个自旋锁吗?谬矣 。”说着拿笔在小明写的自旋锁上画了一个大大的X,然后一边打开电脑,一边继续说:“让我们看看你错在哪里 。”说着啪啪啪的在编译器里敲出了和小明同样逻辑的代码,编译之后,通过代码调试工具查看了函数的汇编代码:
`:
: pushq %rbp
: movq %rsp, %rbp
: movq %rdi, -0x8(%rbp)
: movq -0x8(%rbp), %rax
: cmpl $0x0, (%rax)
: je
【PostgreSQL数据库锁机制——自旋锁浅析】

PostgreSQL数据库锁机制——自旋锁浅析

文章插图
: jmp
: movq -0x8(%rbp), %rax
: movl $0x1, (%rax)
: movq -0x8(%rbp), %rax
: movl (%rax), %eax
: popq %rbp
: retq
: nopl (%rax)
大明指着屏幕上的汇编代码说:“这段代码是无法作为锁的实现的,因为上面的操作缺乏原子性,比如对lock对应的值是否为0(*lock != 0)的判断就不是一个原子操作,它是通过movq -0x8(%rbp), %rax和cmpl $0x0, (%rax)两个指令来实现 。假如有两个线程来获取同一个锁,那么会发生什么情况呢?”说着大明在纸上画了一个示意图:
“你看,这时候问题就出现了,线程A和线程B同时觉得当前的lock中的值是0,也就是说他们两个都能获得锁资源,也就出现了两个线程同时获得锁的情况,你觉得这还合理吗?”
小明羞愧的低下了头,怪不得自己二十多年到头来,还在人海里浮沉,原来是因为不懂汇编语言啊 。
看着小明羞愧的表情,大明解释道:“C语言的一条语句可能对应几条汇编指令,在执行期间无法保证它的原子性,所以在高级语言的层面需要借助一些算法或者底层的指令来实现临界区的同步功能,如果想通过高级语言的算法来实现临界区的同步功能,也不是补可以,但是需要使用一些比较精妙的算法(比如算法),这些方法都有很大的局限性,现在已经很少使用这些方法了,今天我们需要关注的是目前的计算机提供的一些硬件指令,通过这些指令,我们可以借助这些指令来实现临界区的同步功能 。”
TAS VS CAS
小明怯怯的问道:“是不是我们学操作系统课程的时候说的那些原子指令?”
“对的,现在大部分硬件都支持两种类型的原子操作指令,比如TAS和CAS指令,下面我们给出他们的伪码 。”说着,大明拿了一张新的白纸,边写边说道:“TEST-AND-SET简称为TAS,它的流程是向内存变量写入1,然后返回内存变量的原值,伪码是这样的 。”
int TAS(int *lock) {inttemp = *lock;*lock= 1;return *lock;}
“-AND-SWAP简称CAS,它的流程是比较锁中的值和期望值,如果锁中的值和期望值相同,则设置为新值,返回true,否则不设置新值,返回false,它的伪码是这样的 。”大明继续写道 。
int CAS(int *lock, int expect, int new) {if(*lock== expect) {*lock = new;return true;}return false;}
大明提示道:“注意,上面的伪码是借用高级语言的形式来描述这两种类型的指令的含义,实际上它是一个完整的原子操作,在X86架构的CPU中,分别提供了XCHG指令和指令来实现TAS和CAS操作,在X86架构下采用的是基于XCHG指令的TAS来实现的自旋锁 。”
小明兴奋的说:“我已经迫不及待的想看看是怎么使用TAS指令来实现自旋锁的了 。”
大明看着小明手舞足蹈的神态,做了一个的手势,然后说:“稍安勿躁,不要急,再过一会精神病院的救护车就到了 。在这之前我们先来谈一下TTAS 。”