并发控制:互斥
并发产生的原因
首先我们知道并发是因为多线程对于共享资源进行访问,然后导致共享资源出现意料之外的问题,其实我们可以理解为并发的本质就是发生了中断,使得原来顺序执行的行为被打破,才导致了不确定的行为的出现
阻止并发的发生
- 关中断
在上面我们说了,并发的发生主要是因为中断导致原来顺序执行的线程不再顺序执行,然后出现了不确定的行为,那么如果我们呢关闭了中断是不是就可以保证一个共享资源不会被多个线程同时访问
答案明显是不行 - 如果我们关闭了中断,那就是说整个处理器都不能被打断,只能将当前的线程运行完后再运行其他的,那么这就退化成为了顺序执行,就失去了并发的高效性
- 如果我们把中断关了那么如果出现了一些信号,或者操作系统的一些严重的错误,就不能从强制的让cpu去执行那些中断函数,可能会导致系统的崩溃
- 普通的程序是没有权限来关闭中断的
- 对于单处理器系统关中断或许可以避免并发,但是多处理器系统却不行,因为每个处理器内部都有独立的寄存器,而中断是每个处理器的内部的状态
- 使用load/store实现互斥
就是使用load / store这些指令再加上算法来实现两个线程的互斥
Perterson Protocol
若希望进入临界区,按顺序执行以下操作: - 举起自己的旗子 (store)
- 把写有对方名字的字条贴在临界区门上 (store; 覆盖)
然后进入持续的观察模式: - 观察对方是否举旗 (load)
- 观察临界区门上的名字 (load)
- 对方不举旗或名字是自己,进入厕所,否则继续观察
出临界区后,放下自己的旗子 - 不用管门上的字条
这样就可以实现一个简单的互斥代码
def T1():
while True:
heap.x = ‘🏴’
sys_sched()
heap.turn = ‘❷’
sys_sched()
while True:
t = heap.turn
sys_sched()
y = heap.y != ‘’
sys_sched()
if not y or t == ‘❶’:
break
sys_sched()
heap.cs += ‘❶’
sys_sched()
heap.cs = heap.cs.replace(‘❶’, ‘’)
sys_sched()
heap.x = ‘’
sys_sched()
def T2():
while True:
heap.y = ‘🏁’
sys_sched()
heap.turn = ‘❶’
sys_sched()
while True:
t = heap.turn
sys_sched()
x = heap.x
sys_sched()
if not x or t == ‘❷’:
break
sys_sched()
sys_sched()
heap.cs += ‘❷’
sys_sched()
heap.cs = heap.cs.replace(‘❷’, ‘’)
sys_sched()
heap.y = ‘’
sys_sched()
def main():
heap.x = ‘’
heap.y = ‘’
heap.turn = ‘’
heap.cs = ‘’
sys_spawn(T1)
sys_spawn(T2)
但是同样的,Peterson算法在现实中的使用并不多,因为这个假设在多处理器系统中是不正确的。
如何在多处理器中实现并发编程
- 在上面的代码中我们可以知道的是,在软件上实现多处理器的并发编程是比较困难的,所以为什么我们不在硬件上做文章呢
现实生活中的并发
为什么不能就 “等在门口” 呢? - 上一个人出来了,我再进去呗!
int status = 1;
void lock(){
retry:
if(status != 1){
goto retry;
}
status = 0;
}
void unlock(){
status = 0;
}
这就需要跨语句的原子指令
软件不够,硬件来凑
- 原子指令: 一小段时间的”Stop the World“执行
- 一段不可打断的load + 计算 + store
第一个自旋锁
int status = 1;
void lock(){
retry:
int got = atomic_xchg(&status, 0);
if(got != 1){
goto retry;
}
}
void unlock(){
atomic_xchg(&status, 1);
}
这样,一个基本的自旋锁就被我们实现了