理解和使用互斥锁 API
在多处理器系统上实现互斥
理解并发的另一个工具:把线程想象成人、把共享内存想象成物理世界
线程 (我) 想不被别人打断地做一件事
互斥 (mutual exclusion),“互相排斥”
lock_t
数据结构和 lock/unlock
API:typedef struct {
...
} lock_t;
void lock(lock_t *lk); // 试图获得锁的独占访问,成功获得后返回
void unlock(lock_t *lk); // 释放锁的独占访问
一把 “排他性” 的锁——对于锁对象 lk
lock
和 unlock
之间,则其他线程的 lock
不能返回失败的尝试
成功的尝试
困难之处在于:
并发编程:从入门到放弃
即便是一条指令,也不能保证原子性 sum.c
sum = 12615418
sum
应该完全正确才对sum = 9894505
add
可以看成 t = load(x); t++; store(x, t)
假设硬件能为我们提供一条 “瞬间完成” 的 load-exec-store 的指令
int xchg(volatile int *addr, int newval) {
int result;
result = *addr;
addr = newval;
return result;
}
LOCK
指令前缀例子:sum-atomic.c
sum = 40000000
x86 的原子操作保证:
xchg
int xchg(volatile int *addr, int newval) {
int result;
asm volatile ("lock xchg %0, %1"
: "+m"(*addr), "=a"(result) : "1"(newval) : "cc");
return result;
}
xchg
实现互斥如何协调宿舍若干位同学上厕所问题?
实现互斥的协议
int table = KEY;
void lock() {
retry:
int got = xchg(&table, NOTE);
if (got != KEY)
goto retry;
assert(got == KEY);
}
void unlock() {
xchg(&table, KEY)
}
int locked = 0;
void lock() {
while (xchg(&locked, 1)) ;
}
void unlock() {
xchg(&locked, 0);
}
并发编程:千万小心
在共享内存上,共享资源的访问太危险 (原子性、顺序、可见性的丧失),互斥用来阻止代码块之间的并发,实现 “串行化”
测试一下实现是否正确
简单粗暴:锁住总线,内存就是我的了
在 L1 cache 层保持一致性 (ring/mesh bus)
L1 cache line 根据状态进行协调
Further reading: x86-TSO: A rigorous and usable programmer's model for x86 multiprocessors
考虑常见的原子操作:
reg = load(x); if (reg == XX) { store(x, YY); }
reg = load(x); store(x, XX);
t = load(x); t++; store(x, t);
它们的本质都是:
LR: 在内存上标记 reserved (盯上你了),中断、其他处理器写入都会导致标记消除
lr.w rd, (rs1)
rd = M[rs1]
reserve M[rs1]
SC: 如果 “盯上” 未被解除,则写入
sc.w rd, rs2, (rs1)
if still reserved:
M[rs1] = rs2
rd = 0
else:
rd = nonzero
只要 lr/sc 满足顺序/可见性/原子性,lr 到 sc 之间的区域就是原子的!
void do_lrsc(){
while (1) {
[1] t = lr(x);
[2] t = f1/f2(t); // 不同线程可以执行不同操作
[3] if (sc(x, t) == SUCC) {
[4] break;
}
}
}
int cas(int *addr, int cmp_val, int new_val) {
int old_val = *addr;
if (old_val == cmp_val) {
*addr = new_val; return 0;
} else
return 1;
}
cas:
lr.w t0, (a0) # Load original value.
bne t0, a1, fail # Doesn’t match, so fail.
sc.w t0, a2, (a0) # Try to update.
bnez t0, cas # Retry if store-conditional failed.
li a0, 0 # Set return to success.
jr ra # Return.
fail:
li a0, 1 # Set return to failure.
jr ra # Return
BOOM (Berkeley Out-of-Order Processor)
lsu/dcache.scala
s2_sc_fail
的条件 (s2 是流水线 Stage 2)什么是 “CPU 的实现”?
本次课内容与目标
Take-away messages