本次實驗比較簡單,相較于前幾屆需要在linux0.11 底下實現信號量,這次只需要在linux下寫個利用信號量解決生產者消費者問題已經很簡單了.
實驗要求:
在Ubuntu上編寫應用程序“pc.c”,解決經典的生產者—消費者問題,完成下面的功能:
- 建立一個生產者進程,N個消費者進程(N>1);
- 用文件建立一個共享緩沖區;
- 生產者進程依次向緩沖區寫入整數0,1,2,...,M,M>=500;
- 消費者進程從緩沖區讀數,每次讀一個,并將讀出的數字從緩沖區刪除,然后將本進程ID和數字輸出到標準輸出;
緩沖區同時最多只能保存10個數。
要求用信號量來解決問題:
pc.c中將會用到sem_open()、sem_close()、sem_wait()和sem_post()等信號量相關的系統調用
要用到的函數說明:
int fseek(FILE *stream, long offset, int fromwhere);
函數設置文件指針stream的位置。
如果執行成功,stream將指向以fromwhere為基準,偏移offset(指針偏移量)個字節的位置,函數返回0。
如果執行失敗(比如offset超過文件自身大小),則不改變stream指向的位置,函數返回一個非0值。
size_t fread(void *buffer,size_t size,size_t count, FILE *stream );
buffer 是讀取的數據存放的內存的指針
size 是每次讀取的字節數
count 是讀取次數
stream 是要讀取的文件的指針
從一個文件流中讀數據,最多讀取count個元素,每個元素size字節,如果調用成功返回實際讀取到的元素個數,如果不成功或讀到文件末尾返回 0。
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
(1)buffer:是一個指針,對fwrite來說,是要獲取數據的地址;
(2)size:要寫入內容的單字節數;
(3)count:要進行寫入size字節的數據項的個數;
(4)stream:目標文件指針;
(5)返回實際寫入的數據項個數count。
思路:建立文件緩沖區
0-9位存生產的數據,第10位存儲當前讀到的位置.
消費者讀取的位置的時候要執行兩次,一次讀出當前所讀位置,第二次根據此位置計算位偏移;
fseek( fp, 10*sizeof(int), SEEK_SET );
fread( &Outpos, sizeof(int), 1, fp);
fseek( fp, Outpos*sizeof(int), SEEK_SET );
fread( &costnum, sizeof(int), 1, fp);
下面是實驗所需文件pc.c:
#define __LIBRARY__
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#define ALLNUM 550
#define CUSTOMERNUM 5
#define BUFFERSIZE 10
void Producters(pid_t pid,FILE *fp);
void Customer(pid_t pid,FILE *fp);
sem_t *empty,*full,*mutex;
FILE *fp=NULL;
int Inpos=0;
int Outpos=0;
int main()
{
int i,j,k;
pid_t producter;
pid_t customer;
empty=(sem_t *)sem_open("empty",O_CREAT,0064,10);
full=(sem_t *)sem_open("full",O_CREAT,0064,0);
mutex=(sem_t*)sem_open("mutex",O_CREAT,0064,1);
//開啟三個信號量
fp=fopen("products.txt","wb+");
fseek(fp,10*sizeof(int),SEEK_SET);
fwrite(&Outpos,sizeof(int),1,fp);
fflush(fp);
producter=fork();
if(producter==0)
{
Producters(producter,fp);
}
for (i=0;i<CUSTOMERNUM;i++)
{
customer=fork();
if(customer==0)
{
Customer(customer,fp);
}
}
wait(NULL);
wait(NULL);
wait(NULL);
wait(NULL);
wait(NULL);
wait(NULL);
//開了6個進程
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
fclose(fp);
return 0;
}
void Producters(pid_t pid,FILE *fp)
{
int i=0;
for (i=0;i<ALLNUM;i++)
{
sem_wait(empty);
sem_wait(mutex);
fseek( fp, Inpos * sizeof(int), SEEK_SET );
fwrite(&i,sizeof(int),1,fp);
fflush(fp);
Inpos=(Inpos +1) % BUFFERSIZE;
sem_post(mutex);
sem_post(full);
}
return;
}
void Customer(pid_t pid,FILE *fp)
{
int j,productid;
for (j=0;j<ALLNUM/CUSTOMERNUM;j++)
{
sem_wait(full);
sem_wait(mutex);
fflush(stdout);
fseek(fp,10*sizeof(int),SEEK_SET);
fread(&Outpos,sizeof(int),1,fp);
fseek(fp,Outpos*sizeof(int),SEEK_SET);
fread(&productid,sizeof(int),1,fp);
printf("%d: %d\n",getpid(),productid);
fflush(stdout);
Outpos=(Outpos+1)% BUFFERSIZE;
fseek(fp,10*sizeof(int),SEEK_SET);
fwrite(&Outpos,sizeof(int),1,fp);
fflush(fp);
sem_post(mutex);
sem_post(empty);
}
return;
}
報告:
(1)在pc.c中去掉所有與信號量有關的代碼,再運行程序,執行效果有變化嗎?為什么會這樣?
執行結果Customer的消費數據沒有按遞增的順序輸出,而且且fread()函數將產生錯誤;
原因:
因為沒有信號量P(S)控制,導致生產者可能在緩沖區滿后繼續生產,導致沒有被消費的數據被覆蓋,使得消費者消費的數據不是遞增序列。
同時,沒有信號量V(S)控制,導致消費者可能在讀取所有數據后仍然繼續讀取,導致讀取的數據無效。
沒有mutex信號量控制導致出現多進程并發訪問緩沖區,導致出現fread()錯誤。
(2)實驗的設計者在第一次編寫生產者——消費者程序的時候,是這么做的:
Producer()
{ P(Mutex); //互斥信號量
生產一個產品item;
P(Empty); //空閑緩存資源
將item放到空閑緩存中;
V(Full); //產品資源
V(Mutex);
}
Consumer()
{ P(Mutex);
P(Full);
從緩存區取出一個賦值給item;
V(Empty);
消費產品item;
V(Mutex);
}
這樣可行嗎?如果可行,那么它和標準解法在執行效果上會有什么不同?如果不可行,那么它有什么問題使它不可行?
這樣做不可行,只有當緩沖區可寫或者可讀時,才能鎖定該臨界資源,否則容易出現緩沖區未鎖定(mutex=1),consumer鎖定該緩沖區,卻發現empty=10,full=0,等待緩沖區有字符信號量,這樣程序會進入死鎖狀態。