系统相关
首页 > 系统相关> > 操作系统期末复习——第六章 进程同步

操作系统期末复习——第六章 进程同步

作者:互联网

第六章 线程同步

1.背景

防止竞争条件问题——多个进程共享数据,需要保持数据的一致性

防止竞争条件的关键:确保操作共享数据的代码段执行同步(互斥运行)

2.临界区问题

多个进程同时操作共享数据时,每个进程操作共享数据的代码段,这个代码段成为临界区

解决竞争条件的关键:

  1. 确保单个进程在临界区内执行
  2. 确保其他进程可以进入临界区问题

解决临界区问题需要满足如下条件:

  1. 互斥
  2. 前进:没有进程在临界区,确保其他进程能进入临界区
  3. 有限等待:一个进程请求进入临界区,等待时间必须有限

3.Peterson's算法

前提条件:加载和存储指令是原子指令,不可被中断

设置两个变量:

int turn//表示哪个进程可以进入临界区
bool flag[n]//表示哪个进程想要进入临界区,n为进程数量
    
do{
    flag[i] = true;
    turn = i;
    while(flag[j] && yurn == j);
    //临界区代码
    flag[i] = false;
    //退出区代码
}while(true);

4.硬件同步

单、多处理器系统都可以通过禁用中断的方式解决临界区问题,单是代价高!

硬件同步主要采用原子指令(不可中断)——TestAndSet() / swap()

TestAndSet()指令:

bool TestAndSet(bool* lock){
//lock代表临界区是否上锁,true则上锁、false则不上锁,默认false
    bool old = *lock;
    lock = ture;
    return old;
}

//进程判断代码
do{
    while(TestAndSet(&lock));
    //临界区代码
    lock = false;
    //结束区代码
}

swap()指令

//lock同上
void swap(bool* a,bool* b){
    bool tmp = *a;
    *a = *b;
    *b = tmp;
}

//进程判断代码
while(true){
    old = true;
    while(old == true)swap(&old,&lock);
    //临界区代码
    lock = false;
    //结束区代码
}

Peterson's,TestAndSet,swap都会出现忙等待问题!

5.信号量

无忙等待的同步工具

用S来表示信号量,S为整数变量(一般表示临界资源的数量)

只能通过标准原子操作来访问信号量

  1. wait(S): 也称P(S)

    wait(S){
        while(S<=0);
        S--
    }
    
  2. signal(S): 也称V(S)

    signal(S){
            S++;
    }
    

封锁代码的实现

do{
    wait(S);
    //临界区代码
    signal(S);
    //结束区代码
}while(true);

信号量的实现关键是保证wait()和signal()操作的原子执行,没有两个进程能同时对一个信号量执行以上操作

无忙等待的信号量实现

为了让忙等待的进程挂起,在可以进入临界区时重新启动。

将信号量定义如下:

typedef struct{
    int value;//整数值,资源数量
    struct process *list;
}semaphore

wait和signal定义如下:

wait(semaphore *S){
    S->value--;
    if(S->value < 0){
        //将该进程加入等待队列S->list中
        block();//挂起该进程
    }
}

signal(semaphore *S){
    S->value++;
    if(S->value <= 0){
        //从S->list中取出一个进程P
        wake(P);//唤醒取出的进程P
    }
}

死锁与饥饿

6.经典同步问题

6.1有限缓冲问题

假定缓冲池有n个缓冲项,每个缓冲项能存一个数据项

  1. 缓冲池满不能写
  2. 缓冲池空不能读
  3. 读和写互斥

解决方法:

  1. 定义信号量empty——有几个空缓冲项,初始化为n
  2. 定义信号量full——有几个满缓冲项,初始化为0
  3. 定义信号量mutex——保证读写互斥,初始化为1

生产者(写)代码实现:

while(true){
    //生产一个缓冲项
    wait(empty);
    wait(mutex);
    //写入缓冲池
    signal(mutex);
    signal(full);
}

消费者(读)代码实现:

while(true){
    wait(full);//full>0,只要缓冲项有数据就读
    wait(mutex);
    //读出一个缓冲项
    signal(mutex);
    signal(empty);
    //使用读出的数据
}

6.2读者-写者问题

问题规定:

  1. 读写互斥,写写互斥
  2. 多人读可以同时访问数据,但是要知道读的人数

解决方法:

  1. 定义信号量wrt——读写互斥
  2. 定义countread——记录读的人数
  3. 定义信号量mutex——写countread互斥

写者进程代码实现:

while(true){
    wait(wrt);
    //写
    signal(wrt);
}

读者进程代码实现:

while(true){
    wait(mutex);
    countread++;
    if(countread == 1){//第一位读者加wrt锁
        wait(wrt);
    }
    signal(mutex);
    //读
    wait(mutex);
    countread--;
    if(countread == 0){//最后一位读者释放wrt锁 
        signal(wrt);
    }
    signal(mutex);
}

6.3哲学家用餐问题

问题:可能产生死锁(每个哲学家同时请求他们左手的筷子)

几个解决方案:

7.管程

为了解决不当使用信号量而产生的死锁问题

管程是一种程序结构(很像java中的类)

  1. 采用面向对象的方法,简化进程间同步控制
  2. 任何时刻最多只有一个线程执行管程代码
  3. 可以临时放弃管程的互斥访问,等待事件出现时恢复

为什么引入管程:

  1. 把各进程临界区集中管理
  2. 防止违法同步操作
  3. 用高级语言程序书写便于程序的正确性识别

管程的组成:

  1. 一个锁,控制管程访问的互斥
  2. 0或多个条件变量,管理共享数据的互斥访问
  3. 一个条件变量对应一个等待队列,每个条件变量有以一个wait(),signal()操作

标签:复习,进程同步,signal,信号量,互斥,临界,第六章,进程,wait
来源: https://www.cnblogs.com/zhaoqinmumiand/p/16367422.html