spinlock,也称自旋锁。自旋锁最多只能被一个可执行线程持有。如果一个可执行线程试图获得一个被争用(已经被持有的)自旋锁,那么该线程就会一直进行忙等待,自旋,也就是空转,等待锁重新可用。如果锁未被争用,请求锁的执行线程便立刻得到它,继续执行。
一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋,特别的浪费CPU时间,所以自旋锁不应该被长时间的持有。实际上,这就是自旋锁的设计初衷,在短时间内进行轻量级加锁。
Kernel中的自旋锁不能够在能够导致睡眠的环境中使用。举个例子,一个线程A获得了自旋锁L;这个时候,发生了中断,在对应的中断处理函数B中,也尝试获得自旋锁L,就会中断处理程序进行自旋。但是原先锁的持有者只有在中断处理程序结束后,采用机会释放自旋锁,从而导致死锁。
我们看一下spinlock在pthread中的实现,即pthread_spin_lock:
#ifndef LOCK_PREFIX # ifdef UP # define LOCK_PREFIX /* nothing */ # else # define LOCK_PREFIX "lock;" # endif #endif
int pthread_spin_lock (lock) pthread_spinlock_t *lock; { asm ("\n" "1:\t" LOCK_PREFIX "decl %0\n\t" "jne 2f\n\t" ".subsection 1\n\t" ".align 16\n" "2:\trep; nop\n\t" "cmpl $0, %0\n\t" "jg 1b\n\t" "jmp 2b\n\t" ".previous" : "=m" (*lock) : "m" (*lock));
return 0; }
|
这里需要对汇编代码稍微了解一下。
LOCK_PREFIX是为了在SMP下锁总线,保证接下来一条指令的原子性。
%0这里是*lock的值,先将lock的值减一,如果ZF=0(lock值不为0),跳到下面的2标签处继续执行;否则执行结束(lock值为0)。
关于jne,在Intel白皮书里的解释:
jne: Jump near if not equal (ZF=0). Not supported in 64-bit mode.
下面继续看2标签处的代码:
rep nop为实际上为多个nop指令,具体可以参考1中的讨论。
接着比较lock与0的大小,当发现Lock大于0的时候,跳回到1标签,尝试重新获得锁;否则,跳回到标签2继续进行循环。
--------------------------------------
标签1处的代码,在尝试获得锁的时候,直接将lock值减1,如果获得锁操作失败的时候,实际上lock值已经被减了1。这样会不会有问题呢?
实际上,这个问题不用担心,因为在释放锁的时候,lock的值还会被重新设置为1 ;-)
--------------------------------------
这里有一个地方需要注意的就是.subsection和.previous之间代码,与之前的代码不在一个代码段中。由于考虑到大部分情况下,lock都会成功返回,将lock失败后的操作跟之前的代码分开,会提高高速缓存的效率(有限的高速缓存可以放置更多的数据)。
对于pthread_spin_unlock()就简单很多了,只是简单的将lock值设置为1,并返回0:
.globl pthread_spin_unlock .type pthread_spin_unlock,@function .align 16 pthread_spin_unlock: movl 4(%esp), %eax movl $1, (%eax) xorl %eax, %eax ret .size pthread_spin_unlock,.-pthread_spin_unlock
/* The implementation of pthread_spin_init is identical. */ .globl pthread_spin_init pthread_spin_init = pthread_spin_unlock
|
参考:
阅读(5469) | 评论(0) | 转发(0) |