为什么要同步互斥?
因为虽然并发机制提高了我们的系统整体的执行效率,但进程一直切来切去,也会导致进程运行的不确定性和不可重现性(由于某些操作不是原子操作),所以需要引入同步互斥机制。
同步互斥的基本方法:临界区和管程。
进程之间交互可能导致的几个问题:
- 互斥:一个进程在使用资源时别的进程无法使用
- 死锁:每个进程都占了部分资源,导致互相在等,成死循环
- 饥饿:其他进程轮流占用资源,有一个进程始终得不到资源。
临界区:进程中访问资源时一段需要互斥执行的代码,即这部分时原子操作。
临界区的特点:
- 空闲则入
- 忙则等待
- 有限等待
- 让权等待(不能进入临界区的要释放CPU)
临界区的实现:
-
禁用中断:即把中断给关掉,任何进程都无法打扰它。缺点是这样进程会无法终止,而且临界区可能很长。所以要小心使用,仅限单处理机
-
软件方法:很复杂,而且是忙等待
-
更高级的抽象方法,包括:
锁:通过原子操作指令来实现,操作为acquire()和release()。分为忙等待锁和非忙等待锁,区别是一个等待时占用CPU,一个等待时放入队列中,所以不占用CPU。
优点是简单容易证明,支持多处理器和单处理器。缺点是会造成死锁(低优先级进程占用临界区,高优先级进程强占CPU,互相等待),饥饿(没有确保非忙锁的等待队列是按照进去的顺序的)
信号量:也是通过原子操作指令来实现,是由操作系统来做管理者的一种方法。信号量是指一种系统资源的数量。
它的数据结构是一个变量和两个原子操作:- 一个表示资源数量的变量sem
- 一个P()操作,每当申请一个资源,就把sem减一,如果sem<0,则进程需要等待,否则获得资源
- 一个V()操作,每当用完一个资源,就sem加一。这时如果sem>=0,则处于等待队列的可以获得资源。
信号量可以实现:互斥访问和条件同步。程序员在写程序时很麻烦,而且容易出现死锁
管程:一种类似临界区的同步互斥的方法,它包括一个锁操作和0个或多个条件变量,最突出的特点是,在管程种的进程可以选择提前放弃资源,让别的线程使用。
管程还包括两个与条件变量有关的操作:
- wait()操作,放弃资源的使用,条件变量+1,表示让出资源,将自己阻塞在等待队列中。
- signal()操作,表示释放操作,将等待队列中的线程唤醒,条件变量-1,表示资源要被用了
根据条件变量释放之后的操作,分为hansen管程和hoare管程。前者线程继续执行,后者让等待的线程先执行。