并发与同步
并发:指多个控制流同时执行。
多处理器多任务。一般在多处理器架构下内存是共享的。
单处理器多任务,通过调度器,一会调度这个任务,一会调度下个任务。 共享一个处 理器一个内存。
单处理器任务+中断:
同步: 是为了保证在并发执行的环境中各个控制流可以有效执行而采用的一种编程技术。
临界区、锁、死锁
临界区:在并发执行的程序执行环境中,所谓的临界区(critical section)指的是一个会访问共享资源的代码片段,例如同时访问一个共享设备或者一个共享内存,而且当这样的多指令片段同时访问某个共享资源可能会引发问题,那这些代码片段就叫临界区。
锁: 是一种常见的用来同步的机制。
不可睡眠的锁:不可以离开调度。
可睡眠的锁:可以让锁进入不参与调度的状态。
右边的任务也会抢占到cpu,但是因为左边的任务还没有把锁释放出来,所以右边的指令一直在aquire(lock)处循环。
死锁:当控制流执行路径中会涉及多个锁,并且这些控制流执行路径获取锁的顺序不同时就可能发生死锁。
如何解决死锁:
调整获取锁的顺序,譬如保持一致。
尽可能防止任务在持有一把锁的同时申请其它的锁。
尽可能少用锁,尽可能少并发。 少用多线程。
自旋锁的实现
自旋锁(spin lock)是一种不可睡眠的锁。
两个任务被系统交替着调度,任务A拿到锁后将锁取出来看一下,发现是没锁上的,将锁锁上,继续执行临界区的指令。任务B拿到锁后将锁取出来看一下,发现已经上锁了,于是在for循环里面自旋,此时任务B即是参与了调度也是走不下去的。
但是这段代码是有问题的,因为cpu在调度任务的时候是在机器语言级别的。将锁取出来看一下和锁上这两个动作对应着几条机器码。如果任务调度发生在这几条机器码中,那么有可能任务A将锁取出来看一下的时候,任务B抢占了cpu,将锁取出来看一下,这样两个任务都看到的是锁还没有被锁上。所以希望读取锁和上锁这两个动作可以合为一个原子操作,不可被打断。 于是引入了原子指令
amoswap指令的效果就是,从源寄存器中获取到新值,读取存储器地址的值,保存到目标寄存器中。将新值存入到存储器地址中。返回目标寄存器中原来存储器中的值。
思考:既然看锁和上锁这两个操作可以合为一个原子指令,那么原先临界区的代码是否也可以合成一条原子指令呢,从而不需要借助锁。
另外一种简单粗暴的方式去让临界区代码不被打断:直接关中断。
自旋锁的使用:
自旋锁可以防止多个任务同时进入到临界区。
在自旋锁保护的临界区内不能执行长时间的操作。
在自旋锁保护的临界区内不能主动放弃CPU。
其他同步技术