几个概念
互斥:
- 一个进程占用资源,其他进程不能使用
同步:
- 按一定顺序执行各个进程
死锁:
- 各自占用部分资源,造成循环等待
- 一个例子:低优先级进程拥有临界区资源,高优先级进程拥有CPU,那么高优先级在等待低优先级释放临界区资源,低优先级在等待高优先级释放CPU,造成死锁
饥饿:
- 其他进程轮流占用资源,某个进程一直得不到资源
实现同步的方式
锁
- 禁用硬件中断
- 软件方式
- 高级抽象:基于硬件的原子语句
- 忙等待锁
- 进程在等待过程中,占用CPU时间(并没有调度给其他进程,而是一直在while中等待)
- 无法按先入先出
- 无忙等待锁
- 进程在等待过程中,是进入阻塞状态,释放CPU
信号量
由操作系统提供的协调机制。(与锁同等级,锁是各个线程之间的互相协调,信号量则由操作系统提供)
两个操作:P() 和V()
- P() 用于申请资源,每次将信号量-1,如果信号量<0,则表示资源已用完,进入等待队列
- V() 用于释放资源,每次将信号量+1,如果信号量<=0,则表示还有人在等,于是调用唤醒
由操作系统来保证P V两个操作是原子操作。具体实现上,可能由关闭中断或TSB原语等底层支撑来实现来保证原子操作。
管程 (与临界区同等级)
管程相当于是把对临界区的操作,都封装到某几个函数中。这样只要是需要访问临界区,则需要调用管程。因此所有的同步和互斥操作就可以
都写在这里。
管程一开始有一个锁,用于保证每次进入管程的只有一个进程。
这个锁可以使用信号量来实现。
在管程内部,除了一开始的锁,还有各种条件变量,用于表示各个条件。进程可能由于某个条件变量不满足而进入睡眠,也可能由于不满足一开始的锁
而进入睡眠。
进程一旦进入睡眠,优先唤醒已经在管程中的进程(比如因为某个条件变量不满足而进入睡眠的进程),如果管程内部已经没有进程了,则可以唤醒其他进程
进入管程了。
管程的两个函数:wait signal
- wait
- 等待某个条件变量满足。在满足之前,进入睡眠。优先唤醒已经在管程中,但由于执行signal而睡眠的进程,其次唤醒在管程外面睡眠的进程。
- siganl
- 设置某个条件变量,判断是否有进程在等待此条件。如果没有,直接退出。
- 如果有,则唤醒它,同时自己进入睡眠(自己成为上面所说的“由于执行signal而睡眠的进程”)
管程的出入口
- 入口处显然要有一个锁
- 出口处要判断,是否还有因激活别人而进入睡眠的进程,如果有,唤醒,如果没有了,则释放管程锁,唤醒管程外面的进程
一个例子
- A B C 三个进程,同时想进入管程,只有一个能申请到锁进入,假设为A,BC在管程外面睡眠。
- 管程内进程1
- 管程外进程2
- 在管程中,A由于条件变量X1不满足,进入睡眠,同时唤醒B进入管程
- 管程内进程2 A B
- 管程外进程1 C
- 等待条件变量X1 的进程1 A
- B进入管程,B在某个操作中设置条件变量X1,使其满足,于是B将A唤醒,自己进入睡眠
- 等待条件变量X1 的进程0
- 管程内,由于执行signal而睡眠的进程1 B
- A被唤醒,继续执行,由于条件X2不满足,又进入睡眠,接着唤醒B(此时B是“由于执行signal而睡眠的进程”,因此优先唤醒)
- 等待条件变量X2 的进程1 A
- 管程内,由于执行signal而睡眠的进程0
- B被唤醒,B在管程内执行完毕,退出管程,在管程的出口,判断是否还有由于执行signal而睡眠的进程
- 如果有,唤醒
- 如果没有,释放管程锁,唤醒进程C
- 管程内进程2 A C
- 进程C设置条件变量2 使之满足,唤醒进程A,自己睡眠
- 进程A在管程内部执行完毕,退出管程,在出口处,唤醒C
- C继续执行,退出管程