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


“TTAS?感觉像是TEST AND TEST-AND-SET?”
“是的,TAS虽然已经足够我们使用,但是也带来一个问题,大部分CPU实现TAS的方法是锁住总线,一旦锁住总线就等于一个CPU占用了整个总线,而频繁的锁住总线会降低CPU的使用效率,所以在进入TAS之前,我们可以先做一个粗略的检测,这个检测不在原子操作之中,但是它可以让我们快速的知道目前锁的状态,如果第一个TEST检测到锁已经被占用了,那么我们就再等一会,就不用进行TAS了,这样就避免了锁住总线,如果第一个TEST发现锁没有被占用,那么就值得去做TAS 。”
小明说:“但在第一个TEST和TEST-AND-SET之间,锁的状态可能会被其他CPU上的进程修改掉吧?”
“是的,所以这个TEST只是我们提高性能的一个手段 。比如在数据库中,它就是采用的TTAS的方法 。”说着,大明打开了源代码,找到了TAS实现的部分:
static __inline__ int tas(volatile slock_t *lock) {register slock_t _res = 1;__asm__ __volatile__("cmpb$0,%1\n""jne1f\n""lock\n""xchgb%0,%1\n""1: \n":"+q"(_res), "+m"(*lock)::"memory", "cc");return (int) _res;}
大明一边用鼠标在编译器里划住了tas函数,一边说:“从这段汇编代码可以看出,CMP指令和下面的XCHG不能组成一个原子操作,比如一个线程B已经持有了锁,线程A做了CMP指令也发现了有其他线程已经占有了锁资源,于是他就会JNE跳过XCHG指令,但是很可能在CMP指令之后,线程B就立即释放了锁资源 。”大明在纸上画了一个示意图:
“XCHG指令会尝试交换两个操作数,如果想要获得一个锁,也就是说我们打算在‘锁对应的值是0的情况下把锁的值设置为1’,同时我们还可以用0和1来代表TEST-AND-SET是否成功,那么对上面的汇编代码翻译成高级语言就变成下面这样 。”大明开始在纸上写出了对应于的tas函数的伪码:
static __inline__ int tas(volatile slock_t lock) {register slock_res = 1;if(*lock != 1)//CMP指令{//下面3行是原子操作,对应XCHGint temp = *lock;lock = 1;//同*lock = res;res = temp;}return (int) _res;}
小明听得云里雾里的,只好说:“感觉听着有点吃力,” 大明说:“不要担心嘛,自旋锁的事才刚刚开始,有了TAS我们才能继续实现的自旋锁,让我们来看看自旋锁的实现吧 。”
的自旋锁
大明继续说道:“在有些CPU架构下,没有直接使用TTAS,而是先实现了TAS,然后借用函数(或者说宏)来实现的自旋锁 。”
# (lock) (*(lock) ? 1 : TAS(lock))
“有了我们是不是就很容易实现一个自旋锁了呢?”大明问道 。小明想了想说:“让我来写一下,看看这次能不能实现一个可用的自旋锁 。”说着小明在纸上写了起来:
void spinlock_acquire(int *lock) {while(TAS_SPIN(lock));}
写完之后,小明又推演了几遍,感觉没什么错误了,于是帅气的摇了摇头,说道:“怎么样,是不是很佩服我这样美貌与智慧并重的人?”
“嗯,这个自旋锁是可用的,不过美貌和智慧你都没做到,你做到了病重,精神病院的救护车还没到,我们还有时间继续说自旋锁 。”大明调侃道,“我们还能继续优化它 。比如适当的偷懒,这种自旋目的是不放弃CPU,但也没必要不停的旋,我们可以用空指令来适当的让CPU歇一会,而不必过度旋转,旋转太频繁了浪费电,我们使用nop指令来进行偷懒”,说着大明在的源代码中指出了函数 。
static __inline__ void spin_delay(void) {__asm__ __volatile__(" rep; nop\n");}