生产者-消费者问题详解
admin
2023-07-27 06:20:09
0

1. 前言

  生产者-消费者问题是经典的线程同步问题(我会用java和c分别实现),主要牵扯到三个点:
 一:能否互斥访问共享资源(不能同时访问共享数据);
 二:当公共容器满时,生产者能否继续生产(生产者应阻塞并唤醒消费者消费);
 三:当公共容器为空时,消费者能否继续消费(消费者应阻塞并唤醒生产者生产)。

2. JAVA实现

step0:在java中我们创建线程是通过继承Thread类或者继承Runnable接口并实现他的run方法来实现的,这里我们采用后者
step1:定义一个放馒头的大筐(一个公共的容器类),这个筐具有push方法和pop方法,分别对应往筐中放馒头和从筐中取出馒头。由于在同一个时间段内只能有一个线程访问此方法,so,我们给这两个方法加锁。代码如下:

class SyncStack{//定义放馒头的筐,是栈,先进后出
    int index = 0;//定义筐里面馒头的编号
    WoTou[] arrWT = new WoTou[6];//定义一个引用类型的数组

    public synchronized void push(WoTou wt){//定义往筐里放馒头的方法,由于需要保证在一段特定时间里只能有一个线程访问此方法,所以用synchronized关键字
        while(index == arrWT.length){
            try{
                this.wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
        this.notify();
        arrWT[index] = wt;
        index ++;
    }

    public synchronized WoTou pop(){//定义从筐里往外拿馒头的方法,同理在一段时间只能有一个线程访问此方法,所以用synchronized关键字
        while(index == 0){
            try{
                this.wait();//当前的正在我这个对象访问的这个线程wait
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
        this.notify();//唤醒一个等待的线程,叫醒一个正在wait在我这个对象上的线程
        index --;
        return arrWT[index];
    }
}

step2:分别定义生产者和消费者的类,他们均是不同的线程。给出代码:

class Producer implements Runnable{//定义生产者这个类,是一个线程
    SyncStack ss = null;//声明了筐子的引用变量ss,表示做馒头的人往那个筐里放馒头
    Producer(SyncStack ss){
        this.ss = ss;
    }

    public void run(){
        for(int i=0; i<20; i++){
            WoTou wt = new WoTou(i);//new出一个馒头,该馒头的编号为i
            ss.push(wt);//把第i个馒头放到筐中
            System.out.println(i);
            System.out.println("生产了:" + wt);    
            try{
                Thread.sleep((int)(Math.random() * 200));//每生产一个睡眠1s
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable{//定义消费者这个类,也是一个线程
    SyncStack ss = null;//声明了筐子的引用变量ss,表示吃馒头的人往那个筐里拿馒头
    Consumer(SyncStack ss){
        this.ss = ss;
    }

    public void run(){
        for(int i=0; i<20; i++){
            WoTou wt = ss.pop();//取出一个馒头
            System.out.println("消费了:" + wt);//开始吃馒头

            try{
                Thread.sleep((int)(Math.random() * 1000));
                //每消费一个睡眠1s
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

step3:我们事先定义了一个馒头类,现在给出测试类测试:

/*
wait和sleep的区别
    -1:sleep不需要唤醒,线程不会释放对象锁,属于Thread类
    -2:wait需要notify()方法唤醒,线程会放弃对象锁,属于Object类
*/
public class ProducerConsumer{
    public static void main(String[] args){
        SyncStack ss = new SyncStack();
        Producer p = new Producer(ss);
        Consumer c = new Consumer(ss);

        new Thread(p).start();
        new Thread(c).start();
    }
}

class WoTou{//定义馒头这个类
    int id;//定义馒头的编号
    WoTou(int id){
        this.id = id;
    }
    public String toString(){//重写toString方法
        return "WoTou:" + id;
    }
}

step4:看下测试结果,发现符合我们事先说的那三点:
生产者-消费者问题详解

3. C实现

step0:c语言在Windows下实现线程的需要导入#include头文件,用_beginthread();来开始一个线程,用_endthread();来结束一个线程。具体操作方法,自行百度。
step1:C语言中缓冲区对应公共容器,我们通过定义互斥信号量mutex来实现线程对缓冲池的互斥访问。直接看下代码操作:

#include
#include
#define N 10

//代表执行生产和消费的变量 
int in=0, out=0;

//线程结束的标志 
int flg_pro=0, flg_con=0;

//mutex:互斥信号量,实现线程对缓冲池的互斥访问;
//empty和full:资源信号量,分别表示缓冲池中空缓冲池和满缓冲池的数量(注意初值,从生产者的角度) 
int mutex=1, empty=N, full=0;

//打印测试 
void print(char c){
    printf("%c    一共生产了%d个窝头,消费了%d个窝头,现在有%d个窝头\n", c, in, out, full);
}

//请求某个资源 
void wait(int *x){
    while((*x)<=0);
    (*x)--;
}

//释放某个资源 
void signal(int *x){
    (*x)++;
} 

//生产者 
void produce(void *a){
    while(1){
//      printf("开始阻塞生产者\n"); 
        wait(&empty);   //申请一个缓冲区,看有无其他线程访问 
        wait(&mutex);
//      printf("结束阻塞生产者\n");

        in++;

        signal(&mutex);  
        signal(&full);  //full加一,唤醒消费者,告诉消费者可以消费 

//      printf("结束生产。。。\n");
        print('p');

        Sleep(200);
        if(flg_pro == 1){
            _endthread();
        }   
    } 
} 

//消费者
void consumer(void * a){
    while(1){
//      printf("开始阻塞消费者\n");
        wait(&full);
        wait(&mutex);
//      printf("结束阻塞消费者\n");

        out++;

        signal(&mutex);
        signal(&empty);
//      printf("结束消费。。。\n");
        print('c');

        Sleep(200);
        if(flg_con == 1){
            _endthread();   
        }
    }
} 

//主函数 
int main(){
    _beginthread(consumer,0,NULL);  
    _beginthread(produce,0,NULL);
    //总的执行时间为1分钟 
    Sleep(10000);
    flg_pro=flg_con=1;
    system("pause");
    return 0;
}

step2:注意事项:
  1)用来实现互斥的wait(&mutex);signal(&mutex);必须成对出现在每一个线程中,对于资源信号量的waitsignal操作,分别成对出现在不同的线程中
  2)先执行对资源信号量的wait操作,在执行对互斥信号量的wait操作,不能颠倒否则导致死锁。
step3:测试结果,符合预期:
生产者-消费者问题详解

4. 总结

 现在缺乏的是一种把生活中具体的问题抽象成代码的能力,可能也是对c语言的不熟悉导致的问题,看着我宿舍大神写的代码,真漂亮,由衷的羡慕。熟知并非真知,还得多加思考才是。

相关内容

热门资讯

浙江宣传:“走个面儿”咋就没面... “咱北京两千多万人口,您受累,您走个面儿,把这第一波的票房带起来,咱就有了。”某知名导演的新片首映礼...
辞职声明仅95秒遭质疑,韩国队... 【环球时报综合报道】美加墨世界杯小组赛出局后,韩国队主教练洪明甫当地时间28日在墨西哥的韩国队大本营...
美媒爆料:美军第五舰队总部遭伊... 据美国《华尔街日报》27日报道,其通过对卫星图像、社交媒体视频和五角大楼记录的分析发现,今年2月底至...
英国智库给菲律宾GDP增速“浇... 【环球时报特约记者 叶满】英国经济研究机构凯投宏观发布的最新一期《亚洲经济展望》报告(以下简称“报告...
欧洲持续高温,有华人用冰箱降温... 连日来,欧洲多国迎来罕见极端高温天气,法国、德国、意大利等地气温持续飙升,部分地区突破40摄氏度。受...
伊副外长强调船只须按“伊朗线路... 伊朗外交部副部长加里巴巴迪当地时间29日晚间在接受采访时强调,所有船只均须按照“伊朗线路”通过霍尔木...
委内瑞拉强震已致1719人死亡 当地时间29日,委内瑞拉全国代表大会主席罗德里格斯通报,地震已造成该国1719人死亡,5034人受伤...
铋晟新材料申请氯氧化铋基复合材... 国家知识产权局信息显示,江苏铋晟新材料有限公司申请一项名为“一种氯氧化铋基复合材料及其制备方法和用途...
韩国政府将投资千万亿韩元于AI... 韩国总统李在明29日在总统府青瓦台主持召开会议,公布总额超千万亿韩元的半导体、物理人工智能(AI)和...
以色列防长称以伊可能随时再起冲... △卡茨(资料图)据以色列方面29日消息,以国防部长卡茨当天表示,鉴于复杂的安全局势和在黎巴嫩的军事行...