互斥锁
互斥锁 (mutex lock) 确保关键区同一时刻最多只能有一个线程进入。
评估指标
互斥 (mutual exclusion):最基本的要求。它能够实现关键区的互斥访问吗? 公平 (fairness):饥饿线程? 效率 (performance):锁的开销 (overhead) 几何?
禁用中断
当线程 A 进入关键区后,禁用中断以保证不会切换到其他线程。
- 禁用中断是一个受限操作,将该权限转让给用户程序会产生诸多安全问题
- 多核/多处理器?
- 中断本身十分重要!
OS 代码有时会采取禁用中断实现互斥锁。
原子指令与自旋锁
依赖硬件支持的两种原子指令 (atomic instructions) 来实现互斥锁。
c
int TestAndSet(int *ptr,
int new) {
int old = *ptr;
*ptr = new;
return old;
}
c
void CompareAndSwap(int *ptr,
int expected,
int new) {
int temp = *ptr;
if (temp == expected)
*ptr = new;
return temp;
}
使用这两种原子指令实现一个简单的自旋锁 (spin lock),未能获取 (acquire) 锁的线程在 while 循环中自旋等待 (spin waiting)。
c
int flag = 0;
while (TestAndSet(&flag, 1) == 1);
// or while (CompareAndSwap(&flag, 0, 1));
critical_section();
flag = 0;
评估:
- 能够保证互斥访问
- 不保证公平:可能会有长期饥饿的线程
- 单核效率极低,多核(尤其是核数大致与线程数一致)效率较好
让步
很自然的想法。与其自旋等待占用 CPU 资源,不如主动让出 (yield) 自己的时间片,结束本轮的调度。
c
int flag = 0;
while (TestAndSet(&flag, 1) == 1) // or while (CompareAndSwap(&flag, 0, 1))
yield();
critical_section();
flag = 0;
- 单核线程较多时,开销还是很大
- 仍未保证公平
队列,睡眠与唤醒
Solaris 添加了三个新的系统调用:
park():使当前线程睡眠,不再参与调度(而不仅仅只是让出本轮的时间片)unpark(threadID):唤醒指定的线程setpark():标记当前线程即将进入睡眠,使得若在真正执行park()之前收到了unpark(),park()会立即返回而不会阻塞。
c
typedef struct __lock_t {
int flag;
int guard;
queue_t *q;
} lock_t;
void lock_init(lock_t *m) {
m->flag = 0;
m->guard = 0;
queue_init(m->q);
}
void lock(lock_t *m) {
while (TestAndSet(&m->guard, 1) == 1)
; // acquire guard lock by spinning
if (m->flag == 0) {
m->flag = 1; // lock is acquired
m->guard = 0;
} else {
queue_add(m->q, gettid());
setpark();
m->guard = 0;
park();
}
}
void unlock(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;
}
- 引入的队列保证了公平,线程按照 FIFO 顺序被唤醒
- guard 自旋锁控制队列的入口,但其保护的代码是简单的队列逻辑 (L. 17-25 与 L. 32-37) 而非用户指定的关键区,所以自旋开销很小
- 非自旋锁 flag 控制关键区的入口
为什么需要 setpark()?
- 如果在释放 guard 锁与
park()间恰好发生了中断,其他线程可能会在当前线程沉睡之前就尝试unpark()唤醒它,从而导致当前进程永久睡眠。
阅读
Chapter 28 – Locks (except sections 28.10, 28.11, 28.15, 28.16), OSTEP, http://pages.cs.wisc.edu/~remzi/OSTEP/threads-locks.pdf