4.进程同步与信号量

4.进程同步与信号量

1. 进程同步与死锁——进程同步与信号量(16)

  1. 进程合作:多进程共同完成一个任务
    1.1 司机与售票员
// 司机
while(true)
{
    //等待售票员关门的信号
    启动车辆;
    正常运行;
    到站停车;
}


//售票员
while(true)
{
    关门;
    售票;
    //等待司机到站停车的信号
    开门;
}

1.2 打印机
打印机按照打印队列执行打印,当打印队列中有 6个打印任务,2个进程同时添加打印任务,打印队列的第7个任务是哪个,需要协调

  • 小结
    进程有自己的执行条件,有时执行 有时等待,执行完 可能也要 发出信号,让等待该信号的其他进程执行
    进程同步 就是 进程有序的执行

1.3 生产者—消费者实例

//共享数据
#define BUFFER_SIZE 10 //缓存空间
typedef struct{...} item;
item buffer[BUFFER_SIZE];
int in = out = counter = 0;

//生产者进程

while(true){
    //当产品 counter == BUFFER_SIZE 就不再生产了,缓存区满,生产者停
    while(counter == BUFFER_SIZE)
        ;
    //产品没达到 BUFFER_SIZE,继续生产
    buffer[in] = item;
    in = (in + 1) % BUFFER_SIZE;
    counter++;//生产者不断生产,发信号让消费者走
}


//消费者进程
while(true){
    //缓冲区空,消费者停
    while(counter == 0)
        ;
    item = buffer[out];
    out = (out + 1) % BUFFER_SIZE;
    counter--;//消费者不断消耗,发信号让生产者走
}

多进程配合,需要等待 阻塞
通过 进程的走走停停 来保证 多进程合作的 合理有序
等待是进程同步的核心

1.3.1 发信号存在的问题

//生产者
while(true){
    //当 counter == BUFFER_SIZE,生产者 sleep,不再生产
    if(counter == BUFFER_SIZE)
        sleep();
    ...
    counter++;
    //当生产者发现 counter == 1,又有产品了,唤醒消费者
    if(counter == 1)
        wakeup(消费者);
}

//消费者
while(true){
    //当消费者发现 counter == 0,进入sleep,不再消费
    if(counter == 0)
        sleep();
    ...
    counter--;
    //当消费者发现 counter == BUFFER_SIZE - 1,就可以生产了,唤醒生产者
    if(counter == BUFFER_SIZE - 1)
        wakeup(生产者);
}
  • 问题:
    有多个生产者,P1生产一个item,放入缓冲区,缓冲区满了,P1 sleep
    这时,P1 生产一个item,发现缓冲区已经满了,P1 sleep

    消费者消耗了一个产品,counter == BUFFER_SIZE - 1,给消费者发信号,唤醒P1,P1 wakeup

    消费者再消耗一个产品,counter == BUFFER_SIZE - 2,P2 不能被唤醒了
    解决:
    单纯依靠counter判断缓冲区的个数是不够的,还要知道有多少进程睡眠了

  1. 信号量
  • 2.1 引出信号量
    根据信号量 判断是否等待,是否执行
    信号量 int sem:
    0 或者 负数,说明需要等待,让其他进程执行

0 :没有资源了
负数:欠资源了
正数:资源个数
举例:一种资源的数量是8
信号量 = 2:有2个资源可以使用
信号量 = -2,有2个进程在等待资源

上边生产者消费者的例子:
1.缓冲区满,P1执行,P1 sleep,sem = -1,表示一个进程睡眠了
2.P2执行,发现缓冲区满,P2 sleep,sem = -2,表示2个进程睡眠了
3.消费者C执行,wakeup P1,sem = -1
4.消费者C再执行一次,wakeup P2,sem = 0
5.消费者C再执行一次,sem = 1,消费者进程睡眠
6.P3执行,sem = 0,表示没有资源了

  • 2.2 信号量的定义
    信号量: 1965年,荷兰学者Dijkstra提出用一种特殊整型变量用来记录,信号用来sleep和wakeup
// semaphore ['seməfɔː] n. 信号
struct semaphore
{
    int value;//资源个数
    PCB *queue;//等待该信号量的进程
}

//消费资源,P来自荷兰语 proberen,即test
P(semaphore s);

//生产资源,V来自荷兰语 verhogen,即increment
V(semaphore s);

P(semaphore s)
{
    s.value--;
    if(s.value < 0){
        sleep(s.queue);
    }
}

V(semaphore s)
{
    s.value++;
    if(s.value <= 0)
    {
        wakeup(s.queue);
    }
}
  • 2.3 用信号量解决生产者-消费者问题
    缓存文件 buffer.txt,操作该缓存文件
    同时只能有一个进程在写该文件,定义互斥信号量mutex(互斥量)
    当有进程在操作缓冲区内容时,P(mutex),造成进程等待
    使用完 缓冲区文件,调用V(mutex),释放该文件资源,其他进程可以操作缓冲文件了
//用文件定义 共享缓冲区
int fd = open("buffer.txt");
write(fd, 0, sizeof(int));//写in
write(fd, 0, sizeof(int));//写out

//信号量的定义和初始化
semaphore full = 0;//生产的产品的个数
semaphore empty = BUFFER_SIZE;//空闲缓冲区的个数
semaphore mutex = 1;//互斥信号量

//生产者
Producer(item)
{
    P(empty);//生产者先判断 缓存区个数 empty是否满了,empty == 0,阻塞
    P(mutex);//操作文件前,用mutex防止其他进程操作该文件
    读入in,将item写到in的位置上
    V(mutex);//使用完文件,释放文件资源
    V(full);//生产者生产产品,增加full,消费者就可以消费了
}

//消费者
Consumer()
{
    P(full);//当full == 0,缓冲区空了,阻塞
    P(mutex);
    读入out,从文件中的out位置读出到item,打印item;
    V(mutex);
    V(empty);//消费者消耗产品,增加缓冲区个数,增加empty,生产者就可以继续生产了
}

2. 信号量临界区保护(17)

  1. 温故知新
    通过对信号量的访问和修改,让进程有序推进
    问题: empty值必须是正确的,如果empty错了,就不能有序推进了

  2. 共同修改信号量引出的问题

//生产者
Producer(item)
{
    P(empty);//生产者先判断 缓存区个数 empty是否满了,empty == 0,阻塞
    ...
}

//生产者P1
register = empty;
register = register - 1;
empty = register;

//生产者P2
register = empty;
register = register - 1;
empty = register;

//初始情况
empty = -1; //空闲缓冲区的个数,-1表示有一个进程在睡眠

//一个可能的执行(调度)
P1.register = empty; // P1.register = -1
P1.register = P1.register - 1; // P1.register = -2

P2.register = empty; // P2.register = -1;
P2.register = P2.register - 1; // P2.register = -2

empty = P1.register; // empty = -2
empty = P2.register; // empty = -2

如果正确执行,empty初始值为 -1,P1执行完,empty = -2,P2执行完,empty = -3
上边的例子,empty = -2

所以,信号量empty 需要保护

  1. 竞争条件
  • 竞争条件:和调度有关的共享数据语义错误
    错误是由多个进程并发操作共享数据 引起的
    错误和调度顺序有关,很难发现和调试
    需要加保护,保证调度的正确执行
    有的程序中 会加空循环,减少调度的错误概率,但是不会根本解决调度问题
  1. 解决竞争条件的直观想法
    在写共享变量empty时,给empty上锁, 阻止其他进程访问empty
    受保护的 代码段 一次只允许一个进程进入,不能被分割的代码段称为原子操作
//仍然是上边出错的执行序列
P1.register = empty;
P1.register = P1.register - 1;

P2.register = empty;
P2.register = P2.register - 1;

empty = P1.register;
empty = P2.register;

//执行过程
//P1检查并给empty上锁
P1.register = empty;
P1.register = P1.register - 1;

//P2 检查empty的锁,P2不能执行
//P1继续执行
empty = P1.register;
//P1给empty开锁

//P2检查并给empty上锁,下边三句是原子操作,不能分割
P2.register = empty;
P2.register = P2.register - 1;
empty = P2.register;
//给empty开锁
  1. 临界区
  • 临界区:一次只允许一个进程进入的那一段代码(修改信号量的代码就是临界区)
    P1,P2中修改empty的代码,就是临界区
  • 核心: 找出进程中的临界区代码(读写信号量的代码)
//进程的代码结构
剩余区
进入区
临界区
退出区
剩余区
  • 5.1 临界区代码的保护原则
    基本原则:
  • 互斥进入:如果一个进程在临界区中执行,其他进程不允许进入。进程间是互斥关系
  • 有空让进:若干进程要求进入空闲临界区时,要尽快使一个进程进入临界区
  • 有限等待:从进程 发出进入请求 到 允许进入,不能无限等待
  • 5.2 进入临界区的尝试——轮换法
//进程P0
//turn !=0,P0空转
while(turn != 0)
    ;
//turn = 0,P0进入临界区
临界区
turn = 1;//处理完临界区,置turn = 1
剩余区

//进程P1
//turn != 1,P1 空转
while(turn != 1)
    ;
//turn = 1,P1进入临界区
临界区
turn = 0;//执行完临界区,置turn = 0
剩余区
  • 互斥:
    P0进入临界区的条件是 turn = 0
    P1进入临界区的条件是 turn = 1

  • 问题: turn = 1,P1 可以进入临界区,但是P1被阻塞了,P1没操作临界区,P0不能执行临界区

5.3 进入临界区的尝试——生活中买牛奶的例子

  • 冰箱是共享资源,目的是 冰箱中有一个牛奶
    上边的轮换法类似于 值日,1天中 丈夫和妻子 只有1人买牛奶
    要防止重复购买,丈夫和妻子要交流,谁去买牛奶了,留一个便条。
if(noMilk)
{
    if(noNote)
    {
        leave Note;
        buy milk;
        remove note;
    }
}

设置临界区

//进程P0
//P0要进临界区,打个标记,flag[0] = true
flag[0] = true;
//判断P1的flag,如果flag[1] = true,说明P1在操作临界区,P0空转等待
while(flag[1])
    ;
操作临界区; //flag[1] = false,P0可以操作进阶区
flag[0] = false; //操作完,将flag[0]置为false
剩余区

//进程P1
//P1要进临界区,打标记 置flag[1] = true
flag[1] = true;
while(flag[0])//判断P0的标记,flag[0] = true,说明P0在操作临界区,P1空转等待
    ;
操作临界区; // flag[0] = false,P1可以操作临界区
flag[1] = false;//操作完,置flag[1] = false
剩余区
  • 问题:
    P0进入临界区的条件是 flag[0] = true, flag[1] = false
    P1进入临界区的条件是 flag[0] = false, flag[1] = true
    满足互斥要求,但是 能不能保证 临界区空闲时,可以有进程来执行呢?看下边的执行顺序
// 进程P0
flag[0] = true;

//进程P1
flag[1] = true;

//进程P0,空转
while(flag[1])
    ;

//进程P1,空转
while(flag[0])
    ;

flag[0] = true, flag[1] = true,临界区空闲,P0 P1 的请求 无限等待

  • 买牛奶的例子:
    丈夫要买牛奶,看到 妻子留了便条;
    妻子要买牛奶,看到 丈夫留了便条;
    最后谁都没买,冰箱中没有牛奶

  • 解决:非对称标记
    让一个人更勤劳,比如 让丈夫更勤劳,丈夫发现有便条,一会再查看下 是否有牛奶,如果没有,就要去买牛奶

5.4 进入临界区的尝试——非对称标记

  • 带名字的便条 + 让一个人 更加勤劳
    关键:选择一个进程进入,另一个进程循环等待
//丈夫(A)
leave note A;
while(note B)
{
    do nothing;
}
if(noMilk)
{
    buy milk;
}
remove note A;


//妻子(B)
leave note B;
if(noNote A)
{
    if(noMilk)
    {
        buy milk;
    }
}
remove note B;

5.5 进入临界区的尝试——Peterson算法

  • 结合了 标记 和 轮转 两种思想
//进程P0
//P0执行,只 flag[0] = true,turn = 1
flag[0] = true;
turn = 1;
//判断flag[1] 和 turn,如果flag[1] = true && turn == 1,空转
while(flag[1] && turn == 1)
    ;

临界区; //如果flag[1] = false || turn == 0,进入临界区执行
flag[0] = false;//执行完,置flag[0] = false
剩余区;

//进程P1
//P1执行,置flag[1] = true,turn = 0
flag[1] = true;
turn = 0;
// 如果 flag[0] == true,turn == 0,P1空转
while(flag[0] && turn == 0)
    ;
临界区;// 如果 flag[0] == flase || turn == 1,P1进入临界区执行
flag[1] = false;// 执行完,置flag[1] = false
剩余区;
  • Peterson算法的正确性
  • 满足互斥进入
    P0进入临界区的条件是:flag[0] = true, flag[1] = false || turn == 0
    P1进入临界区的条件是:flag[1] == true, flag[0] = false || turn == 1
    P0 P1 同时进入临界区,flag[0] = flag[1] = true, turn == 0 == 1
    满足有空让进
    比如 临界区空闲,P1阻塞,且不在临界区,则flag[1] == false || turn == 0,P0是可以执行的
    满足有限等待
    比如 P0进入临界区,P0 执行完 阻塞了,此时 flag[0] = true,只要P1执行一次, turn = 0,P0就可以进入临界区了

5.6 面包店算法——解决多进程情况

  • 仍然是 标记和轮转的结合

  • 如何轮转:每个进程都有一个序号,序号最小的进入
    如何标记:进程离开时序号为0,不为0的序号即标记
    面包店:每个进入商店的客户 都获得一个号码,号码最小的先得到服务;号码相同时,名字靠前的先服务。

//每个进程有一个号(num[i])一个标记(choosing[i]),num[i]!=0表示要进入临界区,取号最小的进入
//进程i执行
choosing[i] = true; // 保证只有一个进程在选号
num[i] = max(num[0], ..., num[n-1]) + 1; // num[i]是已有号码的最大值 + 1
choosing[i] = false; //取号结束,置 choosing[i] = false
// 遍历所有进程
for(j = 0; j < n; j++)
{
    //别的进程在选号,空转
    while(choosing[j])
        ;
    // num[j] != 0表示进程j要进入临界区,j < i,进程i空转等待
    while((num[j] != 0) && (num[j], j) < (num[i], i))
        ;
}
临界区
num[i] = 0;
剩余区
  • 正确性分析:

  • 互斥进入
    设Pi在临界区,Pk试图进入,一定有 (num[i], i) < (num[k], k),Pk循环等待
    有空让进
    如果没有进程在临界区中,最小序号的进程可以进入
    有限等待
    离开临界区的进程 想再次进入 一定排在最后(FIFO),所以一个进程 最多等n个进程
    弊端:实现很复杂,一直往后取号,可能会溢出

  1. 硬件原子指令法
  • 6.1 临界区保护的另一类解法
    软件实现很复杂,希望通过硬件解决
    一个进程在操作临界区,另一个进程请求进入临界区,一定发生了调度,能不能阻止这种调度
    调度一定有中断,调度时 会调用schedule
//进程Pi
cli(); //关中断
临界区
sti();//开中断
剩余区

该方式适用于单CPU情况,Linux 0.11是单核的;多CPU时不适用

  • 单CPU情况:
    中断是在CPU上有一个中断寄存器 INTR,发生中断,寄存器打个1,CPU每执行完一个指令(指令是汇编指令,C语言的是语句),看 INTR 是否是1,如果是1,就进入中断处理程序
    一旦设置了 cli(),指令执行完,就不判断 INTR 了

  • 多CPU时不适用:
    多CPU时,执行中断,每个CPU对应的 INTR 都置1
    假设临界区在 CPU1 上,P1在执行,设置了 cli(), CPU1 上再有中断,就不调度了,CPU1 上的临界区可以一直执行
    设 CPU2 在执行P2,设置了 cli(),也不判断中断,P2 也执行
    此时 P1 P2 就都在执行临界区了

  • 6.2 临界区保护的硬件原子指令法
    我们的想法是 执行临界区之前上锁,然后执行临界区,执行完开锁
    计算机中的锁是一个变量,上锁 开锁 就是给变量赋值
    比如用 信号量 mutex 表示锁,metux = 1 表示有1个资源,0 表示没有资源
    锁不能用信号量实现 用信号量表示锁,信号量是一个锁,修改信号量需要保护 即 修改信号量这个锁 还需要个锁

  • 锁由硬件实现保护,临界区不能被打断 是原子指令,硬件原子指令 使锁 上锁 开锁 不被打断

// TestAndSet是操作锁的,不能被打断
boolean TestAndSet(boolean &x)
{
    //该函数代码 一次执行完毕
    boolean rv = x;
    x = true;
    return rv;
}

//进程Pi
// lock = true 表示上锁,TestAndSet返回true,如果锁上了,Pi就空转
while(TestAndSet(&lock))
    ;
临界区; // 没上锁,进入临界区执行
lock = false; // 执行完临界区,解锁
剩余区

满足互斥要求: 当lock = false,进程1 判断 TestAndSet返回false,执行临界区,其他进程此时申请临界区,判断TestAndSet返回true 不能执行,进程1 执行完 临界区,释放锁

  1. 总结
    用临界区保护信号量,用信号量保证同步
    用面包店算法、开关中断方法、硬件原子指令算法等 保证信号量是正确的,实现进程的合理推进
  • 面包店算法:每个进程有一个号(num[i])一个标记(choosing[i] 用于取号),num[i]!=0表示要进入临界区,取号最小的进入
    开关中断方法:cli() sti(),适用于单CPU情况
    硬件原子指令算法:用锁实现 临界区的保护,硬件实现锁的保护

3. 死锁(19)

  1. 引出死锁
    再看生产者-消费者的信号量解法,之前的例子:
//用文件定义 共享缓冲区
int fd = open("buffer.txt");
write(fd, 0, sizeof(int));//写in
write(fd, 0, sizeof(int));//写out

//信号量的定义和初始化
semaphore full = 0;//生产的产品的个数
semaphore empty = BUFFER_SIZE;//空闲缓冲区的个数
semaphore mutex = 1;//互斥信号量

//生产者
Producer(item)
{
    P(empty);//生产者先判断 缓存区个数 empty是否满了,empty == 0,阻塞
    P(mutex);//操作文件前,用mutex防止其他进程操作该文件
    读入in,将item写到in的位置上
    V(mutex);//使用完文件,释放文件资源
    V(full);//生产者生产产品,增加full,消费者就可以消费了
}

//消费者
Consumer()
{
    P(full);//当full == 0,缓冲区空了,阻塞
    P(mutex);
    读入out,从文件中的out位置读出到item,打印item;
    V(mutex);
    V(empty);//消费者消耗产品,增加缓冲区个数,增加empty,生产者就可以继续生产了
}
  • 上边的例子 Producer 先调用的 P(empty) 后调用的 P(mutex),如果换个位置呢?
// 设 mutex初值是1,empty初值是0,缓冲区取满了
Producer(item)
{
    P(mutex);  // P(mutex) 会把 mutex 变成 0
    P(empty);  // P(empty) 会把 empty 变成 -1,生产者阻塞
    读入in,将item写到in的位置上
    V(mutex);
    V(full);
}

P(semaphore s)
{
    s.value--;
    if(s.value < 0){
        sleep(s.queue);
    }
}

//消费者
Consumer()
{
    P(mutex);  // mutex是0,执行P(mutex)将mutex变成 -1,消费者阻塞,此时 消费者 和 生产者都阻塞了
    P(full);
    读入out,从文件中的out位置读出到item,打印item;
    V(mutex);
    V(empty);
}
  • 此时 消费者 和 生产者都阻塞了,
    生产者要执行,需要有人把empty释放了,即 消费者要执行 V(empty),当前消费者卡在 P(mutex)
    消费者要执行,需要生产者把 mutex释放了,生产者要执行 V(metux),生产者卡在 上边的 P(empty)

  • 生产者在P(empty)往下执行,依赖于消费者,消费者要往下执行,又依赖生产者P(empty)下边的指令。形成环路等待,死锁。

  • 如果很多进程都没法推进,会导致计算机不工作,CPU利用率低

  1. 死锁的成因

在这里插入图片描述

  • 以车辆占用道路为例:
    车辆 A 行驶在 道路1(上边 左右方向的线) 上,想申请 道路2 (右边 上下方向的线)
    道路2 被 车辆B 占用着,车辆 B 要申请 道路3(下边 左右方向的线)
    道路3 被车辆C 占用着,车辆C 要申请 道路4(左边 上下方向的线)
    道路4 被车辆D 占用着,车辆D 要申请 道路1,然而 道路1 被 车辆A 占用着

  • 死锁的成因:
    资源互斥(比如信号量、打印机),进程占有资源,同时 又再去申请其他资源,造成环路等待,形成死锁

  1. 死锁的必要条件
    互斥使用(Mutual exclusion)
    资源的固有特性,如道口
    不可抢占(No preemption)
    资源只能自愿放弃,如 车开走以后
    请求和保持(Hold and wait)
    进程必须占有资源,再去申请
    循环等待(Circular wait)
    在资源分配图中存在一个环路

4.死锁的处理

  • 4.1 死锁处理方法概述
    • 死锁预防
      破坏死锁出现的条件,不要 占有资源 又申请其他资源
    • 死锁避免
      检测每个资源请求,如果造成死锁就拒绝
    • 死锁检测 + 恢复
      检测到死锁出现时,让一些进程回滚,让出资源
    • 死锁忽略
      好像没有出现死锁一样

4.2 死锁预防的例子

  • 4.2.1 在进程执行前,一次性申请所有需要的资源,不会占有资源再去申请其他资源
    比如 代码第3句 需要一个信号量,第10句需要一个信号量,在程序开始,就获取这2个信号量

  • 缺点:

  • 需要预知未来,变成困难。要知道所有用到的资源,如果代码 if 中用到了 信号量,在程序开始 也要申请该信号量。如果本次执行,不走这个 if,占用的这个信号量就没有被利用
    许多资源分配后 很长时间才使用,资源利用率低

  • 4.2.2 对资源类型进行排序,资源申请必须按序进行,不会出现环路等待

    • 缺点: 造成资源浪费
      对资源进程排序,如果需要10号资源,需要把 10号之前的资源 全部申请了,造成资源的浪费

4.3 死锁避免

  • 4.3.1 安全序列
    安全状态: 如果系统中的左右进程 存在一个可完成的执行序列 P1,… ,Pn,则称系统处于安全状态
    安全序列:所有进程都可以执行完的序列
    在这里插入图片描述

  • 上图含有 5个 进程:P0-P4
    Allocation :占有的资源,以P1 为例,占用资源A 3个,资源B 0个,资源C 2个
    Need:需要的资源
    Available:系统中 剩余的资源

  • 什么方法能让 这些进程都执行完?
    右边的问题中 选项 A 是安全序列
    当前系统 剩余资源时 ABC = 230,给 P1,P1可以执行完,P1 执行完 Available ABC = 532
    P3 可以执行,P3 执行完,Available ABC = 743
    其他进程都可以执行

  • 4.3.2 银行家算法

int Available[1..m]; //每种资源剩余数量
int Allocation[1..n, 1..m];  //已分配资源数量
int Need[1..n, 1..m];  //进程还需的各种资源数量
int Work[1..m];  //工作向量
bool Finifh[1..n];  //进程是否结束

Work = Available;
Finifh[1..n] = false;
while(true)
{
    for(i = 1; i <= n; i++)
    {
        // Need[i] <= Work 这个任务是可以完成的
        if(Finish[i] == false && Need[i] <= Work)
        {
            Work = Work + Allocation[i];  // Work 累加系统曾分配给 i 的资源
            Finish[i] = true;
            break;
        } else {
            goto end;
        }
    }
}

End: for(i = 1; i <=n; i++)
        if(Finish[i] == false)
            return "deadlock";

时间复杂度是 T(n) = O(m* n^2),m是资源数,n是进程数
系统中的 资源和 进程都很多,执行的代价还是很大的

  • 银行家算法实例:
    在这里插入图片描述

  • 应用时,首先假装分配,然后调用 银行家算法,如果给进程1分配资源,进程1执行完,其他进程都不能执行,则拒绝 进程1 的资源申请

4.4 死锁检测 + 恢复:发现问题再处理

  • 定时检测 或者是 发现资源利用率低时检测
    选择哪些进程回滚?回滚的依据是什么,是优先级,还是占有资源多少
    如何实现回滚?那些已经修改的文件怎么办?
    回滚容易出错,比如存款程序,用户已经往账户里打钱,信息已经写到一个文件里了,现在要回滚,这个钱已经在银行了。回滚会出错。

4.5 死锁忽略

  • Windows 和 Linux 都 采用了 死锁忽略的方法
    • 死锁忽略的处理代价最小
    • 这种机器上出现死锁的概率比其他机器低
    • 死锁可以用重启来解决,PC重启造成的影响小
    • 死锁预防让编程变得困难

4.6 总结

  • 死锁预防
    引入太多不合理因素
  • 死锁避免
    每次申请都执行银行家算法,效率太低
  • 死锁检测 + 恢复
    恢复很不容易,进程造成的改变很难恢复
  • 死锁忽略
    死锁出现是不确定的,可以用重启来处理死锁
    大多数非专门的操作系统都用 死锁忽略,如 UNIX,Linux,Windows
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值