本文已参与「新人创作礼」活动,一起开启掘金创作之路。
生产者消费者模型(能够写出伪代码)
生产者和消费者模型,期间就需要线程之间进行通信来实现互斥和同步。
需求是这样的:
- 生产者每生成一个产品,就消耗一个缓冲区,只有当缓冲区不满的时候才能放入;
- 消费者每消费一个产品,就消耗一个产品,只有当缓冲区不空的时候才能消费。
做法:
- 因为缓冲区是临界资源,所以需要定义一个互斥信号量,为 mutex 来实现两者对临界资源的互斥访问。
为了同步生产者和消费者的操作,需要记录缓冲区的剩余大小 empty 和 产品的个数 full。当缓冲区大小不为 0 时,生产者才能放入产品;当产品个数不为 0 时,消费者才能拿走产品。- 注意!不可对临界区先加锁,设想这样一个情况:当 empty = 0 时,生产者此时先对临界区加锁,然后发现缓冲区的数量为 0,则开始进入阻塞等待消费者消费的状态,而此时一个消费者开始进入消耗一个产品,但发现临界区被加锁,所以生产者在等待消费者消费产品,而消费者在等待生产者释放临界区锁,进入了一个死锁状态。
伪代码如下:(临界区就是生产者和消费者的操作)
// P : Wait操作
// V : Signal操作
mutex = 1; // 互斥信号量
empty = N; // 缓冲区的剩余大小
full = 0; // 产品个数
void Producer() {
P(empty); // 生产者生产一个产品,消耗一个缓冲区
P(mutex);
// 往缓冲区放产品 // 临界区
V(mutex);
V(full); // 产品数量加1
}
void Consumer() {
P(full); // 消费者消耗一个产品,释放一个缓冲区
P(mutex); // 临界区上锁
// 从缓冲区拿产品 // 临界区
V(mutex); // 临界区锁释放
V(empty); // 增加一个缓冲区
}
void P(S){ // wait操作,因为有可能wait(阻塞)进程
S--;
if(S < 0) block(); // 如果小于0,代表资源没了
}
void V(S){ // signal操作,因为需要signal(提醒)等待中的进程
S++;
if(S <= 0) wakeUp(); // 如果小于等于0,代表有进程仍然在等待,通知他们ok了
}
死锁发生的条件
- 互斥条件:资源分配是互斥的,资源要么处于被分配给一个进程的状态,要么就是可用状态。
- 等待和占有(请求和保持)条件:进程在请求资源得不到满足的时候,进入阻塞等待状态,且不释放已占有的资源。
- 不剥夺条件:已经分配给一个进程的资源不能强制性地被抢占,只能等待占有他的进程释放。
- 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程释放所占有的资源。
如何避免死锁的发生
预防策略:从形成死锁的条件入手,基本思想就是打破形成死锁的四个条件中的一个或多个,保证系统不会进入死锁状态。
- 破坏互斥条件:比如只读文件、磁盘等软硬件资源可采用这种办法处理。
- 破坏占有和等待条件:在进程开始执行之前,就把其要申请的所有资源全部分配给他,直到所有资源都满足,才开始执行。
- 破坏不剥夺条件:允许进程强行从资源占有者那里夺取某些资源
- 破坏环路等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次执行。
避免死锁:银行家算法
注意预防死锁和避免死锁是不同的概念:
1、预防死锁:破坏死锁四个必要条件中的一个或多个,来防止死锁(较易实现,但由于所施加的限制往往太严格,导致资源利用率和系统吞吐量都较低)
2、避免死锁:不事先采取限制去破坏产生死锁的条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生(实现困难,由于事先只需要较弱的限制条件,所以资源利用率和系统吞吐量都较高)
如果发生了死锁怎么办?
-
死锁检测:发生死锁之前总归需要先检测到死锁吧,不然怎么进行接下来的操作?可以通过检测有向图中是否存在环来检测,从一个节点出发进行 dfs,对访问过的节点进行标记,如果访问到了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。
-
死锁恢复:从下到上逐渐变态
-
撤销进程法:
-
- 撤消陷于死锁的全部进程;
-
- 逐个撤消陷于死锁的进程,直到死锁不存在;
-
-
资源剥夺法:
-
- 从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失;
-
- 从另外的进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态。
-
-
鸵鸟算法,当死锁真正发生且影响系统正常运行时,手动干预(例如重新启动),之所以叫鸵鸟算法是因为这种策略对计算机程序中可能出现的问题采取无视态度(类似于鸵鸟遇到危险时将头埋在地里,装作看不见)
-
举出一个死锁的例子?
以上面的消费者生产者例子为例,如果对临界区的上锁放在了检测缓冲区是否已经满之前就可能发生死锁(可能),因为当empty=0时,此时生产者先对临界区上锁,此时检测缓冲区已经满了,需要消费者去消费产品。但是消费者消费产品之前要先进入缓冲区,但此时缓冲区已经被生产者占有,这样就形成了死锁。