自旋锁属于一种互斥锁。
1 基于硬件原语1:测试并设置指令
原子指令的伪代码:
int TestAndSet(int* old_ptr, int new) {
int old = *old_ptr;
*old_ptr = new;
return old;
}
自旋锁实现:
typedef struct lock_t {
int flag;
} lock_t;
void init(lock_t* lock) {
lock->flag = 0;
}
void lock(lock_t* lock) {
while (TestAndSet(&lock->flag, 1) == 1)
; // 自旋
}
void unlock(lock_t* lock) {
lock->flag = 0;
}
含义:
flag=1表示锁被持有,flag=0表示锁未被持有;
一个线程设置flag=1,就表示这个线程持有了该锁;
一个线程设置flag=0,就表示这个线程释放了该锁;
分析:
(1)如果没有其他线程持有锁时,即flag=0,线程1调用lock(),TestAndSet设置flag=1并返回0, 跳出循环,此时线程1持有该锁。
(2)如果有其他线程持有锁时,即flag=1,线程1调用lock(),TestAndSet不断地设置flag=1并返回1,开始自旋。直到其他线程不再持有锁时,flag=0,TestAndSet设置flag=1返回0,跳出循环,此时线程1持有锁。
2 基于硬件原语2:比较并交换指令
原子指令的伪代码:
int CompareAndSwap(int* ptr, int expect, int new) {
int actual = *ptr;
if (actual == expect) {
*ptr = new;
}
return actual;
}
自旋锁实现:
typedef struct lock_t {
int flag;
} lock_t;
void init(lock_t* lock) {
lock->flag = 0;
}
void lock(lock_t* lock) {
while (CompareAndSwap(&lock->flag, 0, 1) == 1)
; // 自旋
}
void unlock(lock_t* lock) {
lock->flag = 0;
}
含义:
flag=1表示锁被持有,flag=0表示锁未被持有;
一个线程设置flag=1,就表示这个线程持有了该锁;
一个线程设置flag=0,就表示这个线程释放了该锁;
分析:
(1)如果没有其他线程持有锁时,即flag=0,线程1调用lock(),CompareAndSwap设置flag=1并返回0, 跳出循环,此时线程1持有该锁。
(2)如果有其他线程持有锁时,即flag=1,线程1调用lock(),CompareAndSwap不断地返回1,开始自旋。直到其他线程不再持有锁时,flag=0,CompareAndSwap设置flag=1返回0,跳出循环,此时线程1持有锁。
3 基于硬件原语3:获取并增加指令
与前两种相比,可以保证所有线程都能抢到锁。避免一个线程一直自旋。
4 结论
自旋锁的忙等会占用CPU资源,需要使用操作系统级别的原语让出CPU。