目录
1. 同步
协调多线程对共享数据的访问,任何时刻只能有一个线程执行临界区代码。
2. 临界区
定义:对共享内存进行访问的程序片段。
实现方法:
- 禁用中断:没有中断,没有上下文切换,因此没有并发。
- 软件方法:如 Peterson 算法。复杂、需要两个进程间的共享数据项;需要忙等待,浪费 CPU 时间
- 更高级的抽象方法:硬件提供了一些同步原语,操作系统提供更高级的编程抽象来简化进程同步。如锁。
3. 信号量(semaphore)
信号量是操作系统提供的一种协调共享资源访问的方法,用信号量表示系统资源的数量。
一种抽象的数据类型,由一个整型变量(sem)和两个原子操作组成,操作系统保证 PV 操作的原子性。
- P()(尝试减少):sem 减 1,如 sem < 0,进入等待,否则继续
- V()(增加):sem 加 1,如 sem <= 0,唤醒一个等待线程
3.1 信号量实现互斥访问
3.2 信号量实现同步等待
3.3 信号量实现生产者消费者问题
问题抽象:
- 任何时刻只能有一个线程操作缓冲区(互斥访问)
- 缓冲区空时,消费者必须等待生产者(条件同步)
- 缓冲区满时,生产者必须等待消费者(条件同步)
用信号量描述每个约束:
- 二进制信号量 mutex
- 资源信号量 fullBuffers
- 资源信号量 emptyBuffers
代码:
4. 管程
一种用于多线程互斥访问共享资源的程序结构。任意时刻最多只有一个线程执行管程代码,正在管程中的线程可临时放弃管程的互斥访问,等待事件出现时恢复。
组成:
- 一个锁:控制管程代码的互斥访问;
- 0 个或多个条件变量:管理共享数据的并发访问。条件变量是管程内的等待机制,每个条件变量表示一种等待原因,对应一个等待队列。Wait() 操作:将自己阻塞在等待队列中,唤醒一个等待者或释放管程的互斥访问。Signal() 操作:将等待队列中的一个线程唤醒。
管程解决生产者消费者问题:
5. 经典同步问题之哲学家就餐问题
五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。思考时将两支叉子放回原处。如何保证哲学家们的动作有序进行?不会出现有人永远拿不到叉子。
使用信号量解决:
根据哲学家不同编号,使其拿起两只筷子的顺序不同,避免五个哲学家都拿到一部分资源构成环路的情况。
6. 读者-写者问题
对共享数据的读写,同一时刻,允许有多个读者同时读;没有读者时写者才能写,没有写着时读者才能读;没有其他写者时才能写。
使用信号量解决:
读者优先策略:只要有读者正在读状态,后来的读者都能直接进入,如读者持续不断进入,则写者就处于饥饿状态。
写者优先策略:只要有写者就绪,写者应尽快执行写操作;如写者持续不断进入,则读者就处于饥饿状态。