背景
操作系统存在并发问题:竞态条件,即多程序并发存在大的问题
操作系统同步指多线程共享公共资源的协调执行,包括互斥和条件同步。互斥指同一时间只有一个线程可以在临界区执行。
实际条件中,确保同步正确很难。需要高层次的编程抽象(如:锁)和底层硬件支持编译。
信号量和管程是比lock更高级的解决方法。
信号量
信号量(sem)是抽象数据类型:
- 一个整形(sem),两个原子操作(P【减】,V【增】,荷兰语)
- P():sem减1,如果sem<0,等待,否则继续
- V():sem加1,如果sem<=0,唤醒一个等待的IP
信号量的使用
- 信号量是整数,初始值一般设为大于0 的数,在P操作的时候不会阻塞
- 信号量是被保护的变量
- 初始化完成后,唯一改变一个信号量的值的方法是通过P( )/V( )
- 操作必须是原子
- P( )能够使进程阻塞/挂起,V( )能唤醒进程
- 假定信号量是“公平的”
- 没有线程被阻塞在P( )仍然阻塞如果V( )被无限频繁调用(在同一个信号量)
- 在实践中,FIFO经常被使用
- 信号量有两种类型
- 二进制信号量:0/1,与lock达到相同功能
- 一般/计数信号量:可取任何非负值
- 两者相互表现(给定一个可以实现另一个)
- 信号量可以用在两个方面
- 互斥
- 条件同步(调度约束 - 一个线程等待另一个线程的事情发生)
- 用信号量实现互斥
信号量的实现
管程(monitor)
目的: 分离互斥和条件同步的关注
管程:
- 一个锁:指定临界区
- 0或多个条件变量:等待/通知信号量用于管理并发访问共享数据
一般方法:
- 收集在对象/模块中的相关共享数据
- 定义方法来访问共享数据
条件变量Condition Variable:
- 允许等待状态进入临界区
- 允许处于等待(睡眠)的线程进入临界区
- 某时刻原子释放锁进入睡眠
- wait() operation
- 释放锁,睡眠、重新获得锁后返回
- signal() operation(or broadcast() operation)
- 唤醒等待者(或所有等待者),如果有
实现:
- 线程以互斥的方式(lock)进入管程(entry queue)
- 进入管程的线程开始执行管程内部维护的函数
- 执行函数过程中,需要对共享资源进行等待的时候,当前线程挂起在相应的条件变量上,并释放lock(条件变量下是挂起的队列,当满足条件变量的时候,唤醒线程。对条件变量有两个操作:wait x/signal x)
如果在管程中线程A睡眠,会唤醒另一个线程B进入管程,那么在A的lock release前,线程的执行顺序一般是先执行A到lock release然后执行B(Hansen-style)。
经典同步问题
- 读者和写者问题
- 哲学家吃饭问题
信号量和管程的总结
同步结构:
- 锁:互斥
- 条件变量:有条件的同步
- 其他原语:信号量