锁
虽然一直在说“锁可以保护临界区资源”,但是忽然发现笔记中没有任何一篇专门说锁的??!
总述
我的理解是锁“隐式”地把多个线程访问资源的顺序给“串行化”了 —— 先看看锁保护资源的整套流程
- 多个线程约定访问共享状态前获取同一把锁
- 锁通过原子操作保证只有一个线程能获得所有权
- 未获得锁的线程不能进入临界区,只能等待
- 持锁线程完成一组不可被打断观察的操作
- unlock 释放所有权
也就是说,假设一个锁保护了资源,那么各个线程在访问这个资源的时候需要先持有锁(lock),如果一个锁被线程A持有了,那么B就无法持有锁而导致无法访问资源。当A使用完,会归还锁的所有权(瓦达西の心 unlock ),这样其他线程就有机会拥有锁,访问临界区资源 —— 这大抵就是互斥的来源罢
等待锁的方式
假设A已经持有锁了,然后B在等待锁的到来(调用lock失败
那么就延伸出常见的等待策略 ——
自旋
B 不停止运行,而是不断尝试获得锁
C++
while (!TryAcquireLock()) {
// 继续运行,反复检查
}所以此时 B 依旧在占用 CPU
我们常说的busy-waiting(忙等待),或者自旋spinning就是这样的一种方式,不断查询直到成功
那么显而易见的优点 —— 如果 A 释放锁的速度很快,B 就可以不用经过睡眠、唤醒等一系列流程直接开始工作;不过反之,如果 A 长时间不释放,则 B 就会一直在浪费 CPU
阻塞
B 如果 lock 失败,不会一直查询,而是进入休眠等待唤醒,那么涉及上下文切换的开销,如果频繁进行上下文切换必然会影响运行效率
混合等待
先原子尝试获取锁,如果失败则先自旋一段时间,如果依旧lock失败,则进入睡眠,等待唤醒
有一小段简单的代码做个演示 ——
C++
void lock() {
if (TryLock()) {
return;
}
for (int i = 0; i < limited_spin_count; ++i) {
if (TryLock()) {
return;
}
}
BlockCurrentThread();
}