如何用锁实现临界区
临界区是什么?
临界区的四个要求:
- 互斥: 同一时间临界区中最多存在一个线程
- 最终成功(progress):如果一个线程想要进入临界区,那么最终一定可以进入
- 有限等待: 线程 i 想要进入临界区,其他在临界区中的线程是有时间限制的
- 无忙等待: 线程 i 在等待进入临界区时,它可以被挂起,不占用资源
总结 : 临界区是进程之间访问共享资源的地方,只能有一个线程在里面操作。
锁是如何实现的?
线程拿到锁才可以执行,所以给锁两个方法。获得锁和释放锁。
Acquire() 拿到锁
Release() 释放锁,通知别的等待进程
我们只要实现这两个方法就实现了锁。
操作系统提供了一些原子操作指令。其中我们可以用test-and-set 指令来实现锁。
测试和置位指令(Test-and-Set)
- 从内存单元读取值
- 测试该值是否为1 ,为1返回true,不为1返回false。
- 内存单元设置为1
这三步为一个整体,统一为一个原子操作,不会被中断。
用这个原子操作来实现一个自旋锁
自旋锁: 拿不到锁在等待的进程会一直处于等待状态,浪费时间和资源。
class Lock(){
int value = 0;
}
Accquire(){
while(test-and-set(value)); // value 为1一直在这循环也叫自旋
// value 变为 0 了,拿到锁了可以进入临界区了,并且此时test-and-set自动把value设置为1,让别的进程进不来
// 读取临界区的数据.....
}
Release(){
value = 0; // 释放锁,把值为0,让别的锁可以进入
}
这种锁会导致一种死锁的情况:
高优先级别的线程请求锁,来进入临界区,但是临界区中有低优先级别的线程在等待优先级提高来执行,离开临界区。
这两个就互相等待。
用这个原子操作来实现一个无忙等待锁
自旋锁在等待时,进程一直在消耗cpu资源,我们改进一下,使用队列。
class lock{
int value = 0;
WaitQueue q;
}
Accquire(){
while(test-and-set(value)){
add this TCB to wait queue;// 等待时,把这个线程的tcb放入等待队列
schedule; // 让cpu执行调度算法,从就绪队列里调度一个线程执行
}
}
Release(){
value = 0;
remove current_thread from q;// 把当前线程从等待队列移除
wakeup(current_thread); // 把当前线程放入就绪队列
}