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<process.h>头文件,用_beginthread();来开始一个线程,用_endthread();来结束一个线程。具体操作方法,自行百度。
 step1:C语言中缓冲区对应公共容器,我们通过定义互斥信号量mutex来实现线程对缓冲池的互斥访问。直接看下代码操作:

#include<stdio.h>#include<process.h>#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);必须成对出现在每一个线程中,对于资源信号量的wait和signal操作,分别成对出现在不同的线程中
  2)先执行对资源信号量的wait操作,在执行对互斥信号量的wait操作,不能颠倒否则导致死锁。
 step3:测试结果,符合预期:

4. 总结

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