操作系统之互斥记录

忙等待: 

while (1); // 占用CPU资源

 

饥饿:

高个子(进程1)和矮子(进程2)抢篮板(CPU执行权), 矮子抢不到篮板, 矮子饥饿.

 

一. 临界区的四个特点

1. 互斥
2. 前进
3. 有限等待
4. 无忙等待(可选)
临界区很短时, 允许忙等待
临界区很长时, 考虑基于上下文切换实现无忙等待

 

二. 实现互斥的四种方式

1. 禁用硬件中断
OS如何实现多进程?
通过时钟中断, 导致进程切换, 实现并发.

具体方法:
进入临界区 禁用中断
退出临界区 开启中断

局限性:
- 硬件事件无法及时响应
- 临界区长, 影响系统效率
- 多CPU下, 无法实现互斥

 

2.  软件实现
2.1Peterson算法

适用于双进程

Peterson算法的多进程变式:
Eisenberg and Meauire's 算法

两个重要的参数:
flag 需要?
turn 谁来?

伪代码(双进程)

1 process (i) {
2     do {
3         flag[i] = true;
4         turn = j; // 进程i将机会让给进程j
5         while (turn == j && flag[j]); // 进程j把握机会且本身也想进入临界区
6         // 临界区
7         flag[i] = false;
8         } while (1);
9 }

可以使用反证法证明Peterson算法满足临界区互斥, 前进和有限等待的属性.

2.2 Dekker算法

适用于双进程

2.3 面包店算法
适用于多进程

 

3. 原子指令

3.1 锁

两个方法:
acquire: 等待解锁, 获取锁
release: 解锁, 唤醒

锁方法具体实现:

3.1.1 基于test-and-set机器指令

test-and-set内部实现

1 boolean TestAndSet(boolean* target)
2 {
3     boolean rv = *target;
4     *target = true;
5     return rv;
6 }

忙等待版:

 1 class Lock {
 2     int value = 0;
 3 }
 4 
 5 Lock::Acquire() {
 6     while (test-and-set(value));
 7 }
 8 
 9 Lock::Release() {
10     value = 0;
11 }

两种情况:
锁被释放, value=0, value=1且返回0, 跳过while, 进入临界区
锁被占用, value=1, value=1且返回1, 自旋while, 忙等待状态

若临界区很长时, 应该考虑基于上下文切换实现无忙等待

 

无忙等待版:

 1 class Lock {
 2     int value = 0;
 3     WaitQueue q;
 4 }
 5 
 6 Lock::Acquire() {
 7     while (test-and-set(value)) {
 8         add this TCB to wait queue q;
 9         schedule(); // 线程睡眠, 阻塞, 让出CPU
10     }
11 }
12 
13 Lock::Release() {
14     value = 0;
15     remove one thread t from q;
16     wakeup(t); // 唤醒
17 }

3.1.2 基于exchange机器指令

exchange内部实现

1 void Exchange(boolean* a, boolean* b)
2 {
3     boolean temp = *a;
4     *a = *b;
5     *b = temp;
6 }

伪代码:

 1 int lock = 0;
 2 
 3 thread (i) {
 4     int key;
 5     do {
 6             key = 1;
 7             while (key == 1) exchange(lock, key);
 8             // 临界区
 9             lock = 0;
10         } while (1);
11 }

3.2 信号量

可实现同步和互斥

3.2.1 信号量的实现

 1 // 信号量
 2 class Semaphore {
 3     int sem;
 4     // 等待队列
 5     WaitQueue q;
 6 }
 7 
 8 // P操作
 9 Semaphore::P() {
10     sem--;
11     if (sem < 0) {
12         add this thread t to q;
13         block(t);
14     }
15 }
16 
17 // V操作
18 Semaphore::V() {
19     sem++;
20     if (sem <= 0) {
21         remove a thread t from q;
22         wakeup(t);
23     }
24 }

4. 管程(可实现同步或互斥)

schedule和wakeup操作

1 schedule(): 当前thread原地睡眠(醒来继续向下执行), 获取一个就绪thread
2 wakeup(): 将sleep进程 -> 就绪态进程

4.1. 实现管程

管程由Lock和条件变量组成

Lock保证管程中只有唯一一个线程

4.1.1 Lock实现

见上文

4.1.2 条件变量实现

 1 // 条件变量
 2 Class Condition {
 3     // 等待队列默认为空
 4     int numWaiting = 0;
 5     WaitQueue q;
 6 }
 7 
 8 // 条件变量等待操作
 9 Condition::Wait(lock) {
10     numWaiting++;
11     add this thread t to q;
12     // 释放当前锁
13     release(lock);
14     schedule();
15     // 重新获得锁
16     require(lock);
17 }
18 
19 // 条件变量唤醒操作
20 Condition::Signal() {
21     // 等待队列不为空时
22     if (numWaiting > 0) {
23         remove a thread from q;
24         wakeup(t);
25         numWaiting--;
26     }
27 }

4.2. 使用管程

生产者消费者问题

 1 // 缓冲区
 2 class BoundedBuffer {
 3     ...
 4     Lock lock;
 5     Condition notFull, notEmpty;
 6     // 缓冲区产品个数, 默认为空
 7     int count = 0;
 8 }
 9 
10 // 生产者
11 BoundedBuffer::Deposit(c) {
12     lock->Acquire();
13     // 缓冲区为满
14     while (count == n)
15         notFull.Wait(&lock);
16     add c to the buffer;
17     // 缓冲区产品个数增加1, 执行此操作之后, 缓冲区必定不为空
18     count++;
19     notEmpty.Signal();
20     lock->Release();
21 }
22 
23 // 消费者
24 BoundedBuffer::Remove(c) {
25     lock->Acquire();
26     // 缓冲区为空
27     while (count == 0)
28         notEmpty.Wait(&lock);
29     remove c from buffer;
30     // 缓冲区产品个数减少1, 执行此操作之后, 缓冲区必定不为满
31     count--;
32     notFull.Signal();
33     lock->Release();
34 }

 

三. 进程通讯

1. 通讯方式

间接通讯: 管道  消息队列(先进先出msg)  共享内存(shm)

直接通讯: 信号 

2. 阻塞和非阻塞

阻塞 同步
非阻塞 异步

posted on 2018-11-19 16:34  ert999  阅读(317)  评论(0编辑  收藏  举报

导航