启蒙篇
基础篇
- 并发性:
cpu
可以分时间段交替执行不同程序代码
1.临界资源&临界区
-
临界资源:一次只能被一个进程所使用的资源
- eg:
- 硬件——打印机、网卡、键盘
- 软件——共享变量
- eg:
-
临界区:每个进程中访问临界资源的那段代码成为临界区
- 每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看他是否正在被访问
- 若是则进程不能进入临界区
- 若否则可以进入访问,并设置其正被访问的标志
- 每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看他是否正在被访问
2.同步与互斥
进程(线程)之间的两种关系:同步与互斥。
- 所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。
- 所谓同步,是指散布在不同进程之间的若干程序片断,它们的运行必须严格按照规定的 某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。
同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!
总结
- 互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
- 同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
3.互斥的实现
硬件方法
中断屏蔽法
-
- 提要:
- 调度时机大概可以分为三种
- 程序结束时 —— 已经没有竞争对手,不需要互斥
I/O
请求时- 时间片用完时
- 这三种都属于中断
-
只允许
cpu
为一个进程服务 -
中断屏蔽法本质上是将
cpu
“麻痹”,令其无法接收到中断信号,如此一来就无法完成切换操作,自然可以让cpu
只服务于一个进程 -
此种方法只能作用于后两种:
I/O
请求和时间片用完
原子硬件指令法(Test And Set、Swap)
Test And Set、Swap
是一个硬件电路,特点是不会受到中断信号的干扰,无论如何都会执行完指定段代码
软件方法
-
单标志法
- 轮流传递的思想(令牌环网)
- 生死系于别人(缺点)
- 当
turn
的编号与自己编号相符时则使用,使用完毕后更改编号传递至下一个人
-
双标志先检查法 —— 每个人都有自己的标志(无需等待令牌了)
- 通过设置
flag
值的真伪来判断对方是否要使用cpu
。若对方不使用则自己用 - 可以理解为两人相互谦让,但是比较容易产生
bug
- 若双方看对方均为
false
,则同时请求使用cpu
时也会产生冲突 - 若双方看对方均为
true
,则陷入等待
- 若双方看对方均为
- 通过设置
-
双标志后检查法
- 一开始就将
flag
设置为true
,此后再进行判断对方是否请求 - 可以理解为双方争执,可能会出现争执不下而都无法使用(无限等待 + 饥饿)
- 一开始就将
-
Peterson's
算法(真正实现互斥)-
双方开始将
flag
置于true
,但夺取cpu
资源之前会观察对方,避免太过“强势”而造成的无限等待和饥饿问题 -
Faker(){ while(1){ flag[0] = true; //资源是自己的 turn = 1; //留一手,如果你也想要可以先让给你 white(flag[1] && turn == 1){ if(gun == 1){ gun = Faker; }else{ ; } flag[0] = false; } } } Rookie(){ while(1){ flag[0] = true; //资源是自己的 turn = 1; //留一手,如果你也想要可以先让给你 white(flag[0] && turn == 0){ if(gun == 1){ gun = Rookie; }else{ ; } flag[0] = false; } } }
-
如代码所示,
cpu
上优先级上Rookie
更高 -
后台更改数据为最终的数据
-
4.信号量
由来
- P、V操作:
- P(等待):等待进入临界区
- V(释放):从临界区中出来释放资源
工作原理
typedef struct{
int value; //值
struct process *L; //阻塞队列 PCB
}semephore; //信号量
//S.value 的初始值很重要,可以决定最多有几个进程同时运行
//S.value > 0 时 有资源,直接入驻。资源数减一
//S.value = 0 时 刚好无资源,排队等待
//S.value < 0 时 无资源,排队,自己是负几就排第几
void wait(semephore S){
S.value--;
if(S.value < 0){
//本进程放弃cpu,进队本信号量的阻塞队列
}
}
void signal(semephore S){
S.value++;
if(S.value <= 0){
//唤醒本信号量队列的第一个进程,让其等待获取cpu继续运行
}
}
信号量实现同步
此前我们提到了判别符S,其初值尤为重要,可以决定最多有几个进程同时运行
S > 0