开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情 这也是第22篇文章
生产者-消费者问题
废话少说,先上代码
#define N 100
#define true 1
int count=0;
void producer(void){
int item;
while(true){
//产生数据项
item=produce_item();
if(count==N){
sleep();
}
insert_item();
count+=1;
if(count==1){
//说明是从0到1,按理说前面consumer在休眠,应将其唤醒
wakeup(consumer);
}
}
}
void consumer(void){
int item;
while(true){
if(count==0){
sleep();
}
item=remove_item();
count-=1;
if(count==N-1){
//说明是N->N-1,前面producer被抑制,应将其唤醒
wakeup(producer);
}
consume_item(item);
}
}
问题的关键在于:有可能出现其中一个并没有sleep,但是又没有工作,而另一个后面却去唤醒它(自然是不成功的),这样一来wakeup信号量丢失了,最后,前一个就真的永远sleep了,后一个也跟着sleep了。
为什么会出现这种情况?
原因在于竞争条件的出现。而正是访问count变量的时序可能会导致冲突。
假设消费者先访问count,发现它是0,待睡未睡的时候,生产者也读取count值并且给它加了1,再读取发现它变成1了,于是按程序设定去唤醒消费者,但消费者本来就没睡,于是信号量丢失,但之后它才开始执行sleep()(因为它在 if(count==0)的代码间隔时间中,没有去读取新值,它不知道值已经更新了,于是之后就真的睡过去了。)
但按照程序设定,只有当count为1时生产者才会去唤醒消费者,也就是说消费者已经彻底错过了被唤醒的机会。之后随着count==N,生产者也睡过去了,并且不再有去唤醒它的进程。
解决方案
唤醒等待位
前面提到,问题的关键在于第一个wakeup信号丢失了,所以唤醒等待位的作用其实就是记录这个信号。有wakeup信号时该位置1,再次睡眠时该位又清零。
有多少个信号唤醒等待位就设多少位。
- 缺点:治标不治本。对于一两个wakeup信号还好,多了位数和进程数就呈线性关系了,不实际。
信号量(semaphore)
代码表示:
//semaphore
#define N 100
typedef int semaphore;
semaphore full=0,empty=N,mutex=1;//最后一个标记互斥
void producer(void){
int item;
while(true){
item=produce_item();
down(&empty);//对应于P
down(&mutex);
insert_item(item);
up(&mutex);//对应于V
up(&full);
}
}
void consumer(void){
int item;
while(true){
down(&full);
down(&mutex);
item=remove_item();
up(&mutex);
up(&empty);
consume_item(item);
}
}
注意:如果两个down之间的顺序调换,可能会导致死锁。
Q:什么时候会出现?
A:缓冲区完全满时,生产者被阻塞,mutex=0,下一轮消费者执行一下down(&mutex),也被阻塞。
管程
什么是管程
一个由过程、变量及数据结构等组成的集合,组成了一个特殊的模块或软件包。 进程可以任意调用管程中的过程(maybe进程?,不懂原文是不是procedure,指的又是进程还是过程,感觉进程调用进程的说法有点别扭),但不能绕过管程直接访问其中的数据结构。
注意事项
- 管程是一种语言概念,少数语言有,如Java;而c之类的没有(所以不能用c写管程代码,编译器不识别)
- 任一时刻管程中只能有一个活跃进程
- 管程中的 互斥(mutex) 由编译器负责
- 管程中的自动互斥一定程度上保证了时序,比如不会出现wait(类似sleep)之前signal(类似wakeup)的情况 可以将条件变量与管程结合使用
- 条件变量不能当信号量使用,因为发给它的信号可能会丢失。
Q:什么时候?
A:一个没等待进程 的条件变量不会接收信号
Java管程解决生产者——消费者问题
Java没有内嵌的条件变量,但有wait(),等价于sleep();notify(),等价于wakeup(),但不受竞争条件约束。
public class ProducerConsumer{
static final int N=100;
static producer p=new producer();
static consumer c=new consumer();
static monitor mon=new monitor();
public static void main(String[] args) {
p.start();
c,start();
}
static class producer extends Thread{
public void run(){
int item;
while(true){
item=produce_item();
mon.insert(item);
}
}
private int produce_item(){
/*这里写生产代码 */
}
}
static class consumer extends Thread{
public void run(){
int item;
while(true){
item=mon.remove();
consume_item(item);
}
}
private int consume_item(){
/*这里写生产代码 */
}
}
static class monitor{
private int buffer []=new int [N];
private int count=0,lo=0,hi=0;
public synchronized void insert(int val){
if(count==N) go_to_sleep();
buffer[hi]=val;
hi=(hi+1)%N;
count+=1;
if(count==1) notify();
}
public synchronized int remove(){
int val;
if(count==0) go_to_sleep();
val=buffer[lo];
lo=(lo+1)%N;
count-=1;
if(count==N-1) notify();
return val;
}
private void go_to_sleep(){
try {
wait();
} catch (InterruptedException exc) {
// TODO: handle exception
};
}
}
}
消息队列
- 缓冲(mailbox)
#define N 100
#define TRUE 1
void produce(void){
int item;
message m;
while(TRUE){
item=produce_item();
receive(consumer,&m);
build_message(&m,item);
send(consumer,&m);
}
}
void consumer(){
int item;
message m;
for(int i=0;i<N;i++){
send(producer,&m);
}
while(TRUE){
receive(producer,&m);
item=extract_item(&m);
send(producer,&m);
consume_item(item);
}
}
- 取消缓冲——会合(rendezvous)