- 利用原子操作控制变量 uint32_t futex ,控制不同的线程抢占。
- futex = 0 ,说明无人抢占。
- futex = 1 ,只有一个线程抢占(拿到了锁)。
- 其它线程看到 futex 不等于 0 时 ,需要原子设置 futex=2,标记抢占;同时检查设置后获取的 old value ,如果不为0,说明有人先一步抢占,自己需要 wait 。
- futex = 2 ,有线程抢占和等待。
- 此时也需要原子设置 futex=2,检查设置后获取的 old value ,判断自己是不是先一步抢占到。如果不为0,需要wait。
- 抢占锁的线程 释放锁时,调用 futex_wake 唤醒 futex 变量上的线程。
- 利用 futex_wait / futex_wake 控制线程等待和唤醒。
atomic_compare_and_exchange_bool_acq(mem, newval, oldval)
: 如果交换成功,返回0,失败返回非0
/* Atomically store NEWVAL in *MEM if *MEM is equal to OLDVAL.
Return zero if *MEM was changed or non-zero if no exchange happened. */
atomic_exchange_acq(mem, newvalue):
/* Store NEWVALUE in *MEM and return the old value. */
futex_wait, futex_wake 是系统调用。
// uaddr指向一个地址,val代表这个地址期待的值,当*uaddr==val时,才会进行wait
int futex_wait(int *uaddr, int val);
// 唤醒n个在uaddr指向的锁变量上挂起等待的进程
int futex_wake(int *uaddr, int n);
# define LLL_MUTEX_LOCK(mutex) \
lll_lock ((mutex) ->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex))
lock(mutex)
{
/* Normal mutex. */
LLL_MUTEX_LOCK (mutex);
/* 等价于: lll_lock ((mutex) ->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex)) */
/* Record the ownership. */
mutex->__data.__owner = id;
#ifndef NO_INCR
++mutex->__data.__nusers;
#endif
}
void lll_lock(uint32_t* futex)
{
/* 成功设置为 1 时返回 0 ,表示第一次有人 lock;失败返回非0,说明有人用了
1. 期望是 old value = 0, 设置 new value = 1,抢占 futex。
2. 如果有人用了(value=1, 或者 2) ,就需要进入 wait
*/
if (atomic_compare_and_exchange_bool_acq(futex, 1, 0/* old value */)) {
lock_wait(futex);
}
}
void lock_wait(uint32_t*futex)
{
if (*futex == 2) { /* 小优化: 快速进入 wait*/
futex_wait(futex, 2);
}
/* 有3种返回值: =0 时,无人占用。 =1 时,有其它人抢占。 =2 时,有其它人抢占和等待。
如果 atomic_exchange_acq 返回值是0,说明自己是第一个设置 futex=2,可以推出 wait。
1. 设置 futex = 2, atomic_exchange_acq 返回非0(1,2),进入 wait;否则推出循环。
2. 每次唤醒时,重复设置 futex = 2,尝试抢占。
*/
while (atomic_exchange_acq(futex, 2)) {
futex_wait(futex, 2);
}
}
void unlock(mutex) {
mutex->__data.__owner = 0;
if (decr)
/* One less user. */
--mutex->__data.__nusers;
/* Unlock. */
lll_unlock (mutex->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex));
}
void lll_unlock(uint32_t* futex)
{
int oldval = atomic_exchange_acq(futex, 0);
/* 如果仍旧为 1 ,说明之前没人来 lock,只有自己在用,可以不调用 wake 。
1. 解锁时,直接设置为0,同时获取 old value。
2. 如果 old value > 1 (实际是等于2),说明有人在 wait
*/
if (__unlikely(oldval > 1)) {
futex_wake(futex, 1/* number of waiters */);
}
}