author: hxy
date: 2020.3.28
一个不幸的消息是,这一部分是需要大家写程序的。加油吧少年。
进程的同步
所谓 进程同步 ,简单说就是在程序(实际上是进程)并发执行的过程中,进程之间需要互相协作、共享资源时,对资源的一种保护和协调。
比如说,如果你在线看电影,你的在线播放器一般会用两个进程来实现这个功能(也可能用两个线程,这个后面再讲)。为什么呢?因为目前为止在我们的知识里,并发的单位是进程,如果用一个进程的话,就意味着当你的进程分配到一个时间片,它就必须一行一行代码的执行。你可能一个时间片在下载,然后时间片到了之后继续去就绪队列排队,到下一个时间片再播放,总之没办法同时进行。
但是如果用两个进程,那就意味着两个进程需要对同一块内存(存放电影缓存的那块内存)进行操作。假如你家网速比较慢,那就需要下载进程时刻告诉播放进程下载了多少电影了,否则就会出现错误。
与进程同步相关的还有一个概念,叫做 进程互斥 ,它属于进程同步的一种特殊情况,意思是多个进程需要对同一块数据的读写,但是它们不能同时进行操作。
比如你要在网上买火车票,那么这个订票系统里面就存在着一个互斥变量 —— 车票信息。极端情况下,比如在春运的时候,大家可能同时去抢一趟火车的票,在系统里就是有无数进程在同时去操作这个车票信息的变量。我们既要允许这种并发操作的行为,又只能一次让一个进程去修改车票信息,否则就可能会一票多卖了。
进程同步的实现,其实就是对共享变量(或者共享系统资源)进行保护。一个简单的方法就是给变量的状态设置一个信号灯,告诉进程们此时这个变量是否可以操作。
这个信号灯,在操作系统术语中称为 信号量 。
信号量
如果大家还记得一点点编程的知识,那么信号量该怎么实现的话应该心里会有一个答案 —— 那就是给需要保护的变量设置一个bool类型的变量flag就可以了。
如果是车票系统的那种情况,就是只有这个flag为True的时候,才说明这个座位目前没有人在申请购买,也才允许进程进行购买操作。否则的话你就只能一直刷新了。
同时也就意味着,如果一个人准备购买这张车票,那么他的购票进程的得给这个座位上个锁,也就是把flag设置为False,使得其他进程一看就知道这张票已经名花有主了。
根据这个思路,每个购票进程的程序就一目了然了:
while(flag == False) //如果座位有人在操作
{
; //循环体什么都不做,那就干等着刷票吧,万一正在操作的那个人没买呢
}
//这个座位现在没人在操作了
flag = False; //我先把座位锁定了,免得别人操作
buyTicket(); //购票行为
//如果你最终不要这个座位了
flag = True; //释放这个座位的操作权
但是在实际操作中这个方式是不可行的,因为flag的语言层次太高了。
在计算机中,cpu是一条一条指令执行的,而不是一行一行c语言程序。c语言是高级语言,一行c语言程序可能被编译成多行cpu指令。
这是因为,cpu虽然可以跟内存直接打交道,但是它在进行计算的时候,只能对cpu内部的寄存器进行操作。
回顾一下计算机组成原理,存储器的基本上是三级体系:寄存器(位于cpu里面) -> 内存(也就是存储器)-> 外存(硬盘、光盘、u盘等)。这三个级别离cpu越来越远,速度越来越慢,价格越来越低,容量越来越大。
寄存器的数量很有限,一个寄存器也只能存放几个字节的数据。cpu从内存中读 操作指令 (加减乘除等等), 然后把操作数从内存先复制到寄存器里,然后才能进行计算。
所以即使最简单的设置flag的操作,也需要进行三个步骤:
- 将flag从内存复制到寄存器;
- 设置flag的值;
- 把设置完的flag写回内存中。
而时间片可能在任何一个时刻结束,这就无法保证flag一定能够被成功设置。
因此,真正的flag机制无法在应用软件的层面实现,只能由操作系统来帮忙,只有操作系统才可以直接对底层硬件进行控制和操作。
这也是我们需要把flag这种标识符专门叫做信号量的原因,它是操作系统层面的特殊变量,有特殊的控制方法。
下一节学习信号量的几种实现方法。