互斥
并发编程
大家已经掌握了越来越多编写并发程序的工具了!
使用操作系统 (和硬件机制) 实现非自旋的锁
int table = KEY;
void lock() {
retry:
int got = xchg(&table, NOTE);
if (got != KEY)
goto retry;
assert(got == KEY);
}
void unlock() {
xchg(&table, KEY)
}
typedef struct { int locked; } spinlock_t;
#define LOCK_INIT() ((spinlock_t) { .locked = 0 })
void spin_lock(spinlock_t *lk) { while (xchg(&lk->locked, 1)); }
void spin_unlock(spinlock_t *lk) { xchg(&lk->locked, 0); }
多个处理器上的线程争相打印
void thread() {
lock(&lk);
// 单个 printf 保证线程安全
printf("LOG: ");
printf(very_long_string);
unlock(&lk);
}
$ ./a.out > /mnt/sdcard/a.txt # printf 将非常耗时
别忘了操作系统还管理其他进程/线程……
void thread() {
while (1) {
lock(&lk);
// ------------ (中断;线程切换)
do_something();
unlock(&lk);
}
}
int main() {
for (int i = 0; i < 1000; i++) create(thread); // 线程 >> CPU
}
持有锁的线程
等待锁的线程
Simply yield!
用户进程的事,操作系统完全说了算
与自旋锁完全一样,除了在自旋失败后增加一次系统调用:
void yield_lock(spinlock_t *lk) {
while (xchg(&lk->locked, 1)) {
syscall(SYS_yield); // yield() on AbstractMachine
}
}
void yield_unlock(spinlock_t *lk) {
xchg(&lk->locked, 0);
}
如果等待锁的线程非常非常多,我们需要 “空转一轮” 才能让真正需要运行的线程执行。
- 能否干脆暂停这个线程,等有人解锁以后再被调度执行?
t->registers.PC
$\in$ yield_lock(lk)
$\land$ lk->locked == 0
提供两个系统调用:
mutex_lock(&locked)
xchg(&locked, 1) == 1
,就不再调度当前线程locked
”mutex_unlock(&locked)
xchg(&locked, 0);
locked
” 的线程,修改其状态为可调度void mutex_lock(mutexlock_t *lk) {
if (lk->locked != 0) {
append(lk->wait_list, current); // 睡眠
current->status = BLOCKED;
yield(); // 操作系统控制了中断,有调度权
} else {
lk->locked = 1;
}
}
void mutex_unlock(mutexlock_t *lk) {
if (!is_empty(lk->wait_list)) {
pop_front(lk->wait_list)->status = RUNNABLE; // 唤醒
} else {
lk->locked = 0;
}
}
管理员 (操作系统代码) 有一个手环
任何人想进入更衣室都需要通过管理员
自旋锁 (线程直接共享 locked)
睡眠锁 (locked 保存在操作系统内核,通过系统调用访问)
小孩子才做选择,我全都要
pthreads 库中的互斥锁 (pthread_mutex
)
set scheduler-locking on
info threads
; thread X
Futexes are very basic and lend themselves well for building higher-level locking abstractions such as mutexes, condition variables, read-write locks, barriers, and semaphores. —— futex (7)
操作系统会设计怎样的接口?
int futex(int *uaddr, int futex_op, int val, ...);
RTFM
任何体系结构上 uaddr
都是 4B
futex_op == FUTEX_WAIT
)*uaddr == val
就睡眠等待 futex_wake
(原子完成)-EAGAIN
futex_op == FUTEX_WAKE
)val
个正在睡眠的线程void futex_lock() {
while (1) {
int r = xchg(&locked, 1);
if (r == 0) break;
syscall(SYS_futex, &locked, FUTEX_WAIT, 1);
}
}
void futex_unlock() {
xchg(&locked, 0);
syscall(SYS_futex, &locked, FUTEX_WAKE, 1);
}
仍然不是我们想要的 (正确性:futex.yaml)
本次课内容与目标
Take-away messages