虽然说是没啥卵用的东西,但学习证明它为什么是互斥的还是有点意思
定义
箭头
临界区的开始需要调用 lock()
,结束需要调用 unlock()
锁算法的特性
- 【必须】互斥:不同线程的临界区无重叠,设线程
和任意整数 ,要么满足 ,要么满足 - 【必须】无死锁:一个线程尝试获得锁,总会成功获得,否则一定存在其它线程多次执行临界区
- 【可选】无饥饿:每一个试图获得锁的线程最终都能成功
LockOne 算法
LockOne
是一种双线程锁算法,
LockOne
类内部伪代码如下
class LockOne {
bool[] flag = bool[2];
lock() {
i = currentThread.getID();
// 假设线程 ID 只有 0 和 1
// 并且假定这些变量都是满足可见性的
j = i^1;
flag[i] = true; // 我感兴趣
while(flag[j]); // 等待直到对方失去兴趣
}
unlock() {
int i = currentThread.getID();
flag[i] = false;
}
}
对于一个算法,如何较为严格的证明它是互斥(安全)的?
接下来学习使用反证法来证明该算法的互斥
证明互斥
为了防止语文不过关,记
互斥不成立的条件:那就是不满足“要么
观察算法的 lock
过程:
,这是 A 进入临界区的前置动作,read
操作是决定进入临界区的关键- 同理,
考虑不满足互斥的情况下,read
操作),而无需 unlock
,化简得出
很显然这是矛盾的
死锁的存在
再重新观察一下过程
线程在进入
如果线程交叉执行,导致
这样无法执行
这样就进入死锁状态了
要想保证死锁不发生,那就要一个线程在进入临界区后再启动另一个线程
LockTwo
同样是伪代码,
LockTwo {
int x;
lock() {
i = get_threadID();
x = i; // 依然保证可见性
while(x == i);
}
unlock() {}
}
证明互斥
同样的分析过程,观察
要想达成
如果不满足互斥,
这也确定了该情况是矛盾的
单线程阻塞
由上面的过程可看出,这种算法的奇葩在于必须并发执行才能实现互斥(自己看看条件吧,写公式真累)
Peterson 锁
当把 LockOne
和 LockTwo
结合时,则有一个完美的双线程互斥锁算法(满足前面提到的三大特性)
class Peterson {
bool flag[2];
int x;
lock() {
i = id();
j = i^1;
flag[i] = true; // 我感兴趣
x = i; // 但你先走
while(flag[j] && x == i);
}
unlock() {
i = id();
flag[i] = false;
}
}
证明互斥
(一个意思,略)
过滤锁
过滤锁是支持
总之就是地铁站逐层过滤的意思
class Filter<n> { // 伪代码,别较真
int level[n] = {0};
int x[n]; // index0 不使用
lock() {
int id = getID();
for(i:[1,n)) {
level[id] = i;
x[i] = id;
while(level[k] >= i && x[i] == id); // 存在 k != id
}
}
unlock() {
level[getid()] = 0;
}
}
Bakery 锁
class Bakery<n> {
bool flag[n]; // false
int label[n]; // 0
lock() {
id = getID();
flag[id] = true;
label[i] = max(label[0,n))+1;
while(flag[k]&&(label[k]<label[i] || (label[k]==label[i]&&k<i)));
}
unlock() {
flag[getID()] = false;
}
}
多线程互斥总结
对于 Filter 和 Bakery 锁,他们都是互斥、无死锁、无饥饿的
但对于
参考
《多处理器编程的艺术》Chapter.2