voidinit(lock_t *mutex){ // 0 -> lock is available, 1 -> held mutex->flag = 0; }
voidlock(lock_t *mutex){ while (mutex->flag == 1) // TEST the flag (Line 1) ; // spin-wait (do nothing) mutex->flag = 1; // now SET it! (Line 2) }
voidunlock(lock_t *mutex){ mutex->flag = 0; }
这个版本的锁实现很简单。当程序加锁的时候,flag 置为 1,当程序释放锁时,flag 置为 0。当一个线程想要获得锁,但是 flag==0,那么这个线程就会不停的执行 while 循环,直到获得这个锁。这个过程称为自旋。
这个实现有一个很严重的问题,由于处理器的中断,不能保证 Line 1 和 Line 2 之间不会插入别的线程的执行。假设有两个线程 A 和 B,它们都需要访问临界区。如果线程 A 在 Line 1 执行之后被操作系统切换,线程 B 开始执行并且获得了锁,然后再次切换线程 A 执行,这个时候同时有两个线程持有一把锁,锁的正确性不能保证。
因此,锁的实现通常需要借助硬件的特殊指令,保证 Line 1 和 Line 2 之间不会插入其他线程的代码。这种指令称为原子交换(atomic exchange)。一种原子交换指令 TestAndSet 的 C 伪代码实现如下。
当 flag 为 0:把 flag 置为 1,表明锁被占用。 当 flag 为 1 :把线程加入到休眠队列中,等待锁的释放后被唤醒。
unlock
1 2 3 4 5 6 7 8 9 10
voidunlock(lock_t *m){ while (TestAndSet(&m->guard, 1) == 1) ; //acquire guard lock by spinning if (queue_empty(m->q)) m->flag = 0; // let go of lock; no one wants it else unpark(queue_remove(m->q)); // hold lock // (for next thread!) m->guard = 0; }
voidmutex_lock(int *mutex){ int v; /* Bit 31 was clear, we got the mutex (the fastpath) */ if (atomic_bit_test_set (mutex, 31) == 0) return; atomic_increment (mutex); while (1) { if (atomic_bit_test_set (mutex, 31) == 0) { atomic_decrement (mutex); return; } /* We have to waitFirst make sure the futex value we are monitoring is truly negative (locked). */
v = *mutex; if (v>= 0) continue; futex_wait (mutex, v); } }
voidmutex_unlock(int *mutex){ /* Adding 0x80000000 to counter results in 0 if and only if there are not other interested threads */ if (atomic_add_zero (mutex, 0x80000000)) return;
/* There are other threads waiting for this mutex, wake one of them up. */ futex_wake (mutex); }