您好,登錄后才能下訂單哦!
??生產者-消費者問題是經典的線程同步問題(我會用java和c分別實現),主要牽扯到三個點:
?一:能否互斥訪問共享資源(不能同時訪問共享數據);
?二:當公共容器滿時,生產者能否繼續生產(生產者應阻塞并喚醒消費者消費);
?三:當公共容器為空時,消費者能否繼續消費(消費者應阻塞并喚醒生產者生產)。
?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:看下測試結果,發現符合我們事先說的那三點:
?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:測試結果,符合預期:
?現在缺乏的是一種把生活中具體的問題抽象成代碼的能力,可能也是對c語言的不熟悉導致的問題,看著我宿舍大神寫的代碼,真漂亮,由衷的羨慕。熟知并非真知,還得多加思考才是。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。