问题描述
问题描述如图:
代码思想
生产者消费者使用管道进行通信,每次读写1024*sizeof(int)大小的数据。
int fd[10][2];//管道数组
int fd_empty[10]={0};//消费者线程判断管道是否为空数组
生产者要判断管道是否为空,消费者要判断管道内是否含有数据,因此追加声明一个fd_empty[]数组来让生产者消费者线程判断。
由于只有一个生产者,所以生产的时候是顺序生产的,即文件中的数据块为1,2,3,4…。为了生产效率,生产时会遍历管道,寻找到为空的管道将数据填入其中,消费者消费时,会遍历管道,寻找到不为空的管道进行消费。由于生产者生产时填入管道中的数据为乱序,比如1号管道中的数据块可能为1,2,8。而消费者消费时读取到的数据块也无先后顺序,比如先读取到了2号数据块,再读取到1号数据块,此时如果像生产者那样顺序写入文件的话,得到的结果肯定和生产者生产的结果不一样。所以我们需要得到生产者生产的数据块所对应的位置信息。所以我们将fd_empty[]中存储位置信息,比如生产者第n次生产的数据块放入第m个管道中,那么fd_empty[m]=n,消费者可以据此知悉自己所读的数据块的位置是在哪里。
最后用
pwrite(fp,w, once_number* sizeof(int), location*4096);
将数据块写入指定的位置
代码
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/wait.h>
#include<fcntl.h>
#include<sys/stat.h>
int fd[10][2];//管道数组
int fd_empty[10]={0};//消费者线程判断管道是否为空数组
int pthread_number=10;//消费者数量
int once_number=1024;//一次读写大小
int all_ready_read=0;//目前消费者消费次数
int loop_number=262144;//生产(消费)次数
pthread_mutex_t mutex_fd_empty = PTHREAD_MUTEX_INITIALIZER;//给fd_empty数组上锁
void *producer()
{
int i=0;
int *fp;
fp=open("/home/wsk/文档/linuxhomework2/data.txt", O_RDWR |O_CREAT|O_APPEND,S_IRWXU);//生产者写入文件
int fd_number=0;//
int flag=0;//判断是否有管道空闲flag
while(i<loop_number)
{
flag=0;
int *w = (int *)malloc(once_number* sizeof(int));//申请写入数组
//生成随机内容的数组
for(int j=0;j<once_number;j++)
{
w[j]=rand()%10;
}
//寻找是否有管道内没有内容
for(fd_number=0;fd_number<10;fd_number++)
{
if(fd_empty[fd_number]==0)//找到为空管道
{
fd_write_number=fd_number;
flag=1;//有管道可以进行生产
i=i+1;//生产了一次
break;
}
}
if(flag==1)
{
//生产
write(fd[fd_write_number][1],w,once_number* sizeof(int));//向管道内写数据
write(fp,w,once_number* sizeof(int));//向生产文件内写数据
pthread_mutex_lock(&mutex_fd_empty);
printf("fd_write_number=%d\n",fd_write_number);//输出现在在第几管道生产
fd_empty[fd_write_number]=i;//传给消费者位置信息(i-1)这里不用i-1而用i是为了下面判断的时候判断大于0的时候表示管道内有数据 用i的话i=1时候 i-1=0 消费者会错过第一次的生产内容
pthread_mutex_unlock(&mutex_fd_empty);
printf("i=%d\n",i-1);//输出现在生产的次数
}
}
close(fp);
}
void *consumer()
{
int fp;
int flag=0;
if((fp=open("/home/wsk/文档/linuxhomework2/data2.txt",O_RDWR |O_CREAT,S_IRWXU))==-1)//消费者写入文件
{
printf("can't open the file");
}
int *w = (int *)malloc(once_number* sizeof(int));
int location=0;//消费者写入文件的位置
int fd_read_number=0;//读取管道号码
int j=0;
while(all_ready_read<loop_number)
{
flag=0;
fd_read_number=0;
location=0;
pthread_mutex_lock(&mutex_fd_empty);
//判断管道有内容的位置
for(j=0;j<10;j++)
{
if(fd_empty[j])
{
flag=1;
fd_read_number=j;//把位置信息传给fd_read_number
break;
}
}
if(flag==1){
if(read(fd[fd_read_number][0],w,once_number* sizeof(int)))//从管道中读出数据到w指向的数组中
{
printf("pthread_number=%d all_ready_read=%d\n",pthread_self(),all_ready_read);//输出第几个线程和当前的消费次数
printf("fd_read_number=%d\n",fd_read_number);//读取管道号码
location=fd_empty[fd_read_number]-1;//写入文件的位置
printf("location=%d\n",location);
pwrite(fp,w, once_number* sizeof(int), location*4096); //写入到消费者文件中
all_ready_read=all_ready_read+1;
fd_empty[fd_read_number]=0;
}
}
pthread_mutex_unlock(&mutex_fd_empty);
}
close(fp);
}
int main()
{
int i=0;
for(i=0;i<10;i++)
{
if(pipe(fd[i])<0)//创建管道
{
printf("pipe %d error",i);
}
fd_empty[i]=0;//初始化fd_empty数组
}
pthread_t tidconsumer[pthread_number];
pthread_t tidproducer;
pthread_create(&tidproducer,NULL,producer,NULL);//创建生产者线程
//创建消费者线程
int pthread_number2=0;
while(pthread_number2<pthread_number)
{
pthread_create(&tidconsumer[pthread_number2],NULL,consumer,NULL);
pthread_number2=pthread_number2+1;
}
pthread_join(tidproducer,NULL);//判断消费者线程是否跑完
printf("producer done/n");
pthread_number2=0;
//判断生产者线程是否跑完
while(pthread_number2<pthread_number)
{
pthread_join(tidconsumer[pthread_number2],NULL);
pthread_number2=pthread_number2+1;
}
printf("consumer done/n");
return 0;
}
运行过程
结果
用md5和diff判断文件内容相同
踩坑
1.首先是在文件读写方面
fp=open("/home/wsk/文档/linuxhomework2/data2.txt",O_RDWR |O_CREAT,S_IRWXU))
这里在最后加上了S_IRWXU,是为了给文件所有权限,不然创建出来的文件是上锁的状态(只读或者不可读不可写),不能进行后续的操作。
2.内存分配
每次写入读出都是1024字节,这里使用了动态分配即:malloc
int *w = (int *)malloc(once_number* sizeof(int));
如果是声明命名w[1024]也可以。但是直接声明w[1024]是在栈内分配的内存,而用malloc是在堆上分配的内存。栈上分配内存存在着一个上限,而堆上分配内存的上限比栈上分配高。
3.关于上锁
由于生产者生产完的产品放在管道中供消费者去品尝,所以二者都要对fd_empty数组进行操作(生产者生产完了里面放位置信息,消费者消费结束后放0)。因此要对这个共享数组进行上锁。
此外由于消费者线程有10个,他们之间也是并行的,所以消费者们会存在对管道的争夺:比如现在有一个管道内含有数据,因为程序写的是先消费再置0,所以有可能一个消费者正在消费的时候,将其切出,另一个消费者获得了同样的管道接口,结果等第二个消费者想消费的时候管道里早已空无一物(被第一个消费者消费掉了)。因此为了避免这个问题将消费过程整体上锁。
关于为什么不读出管道后马上置0,让其他消费者知道该管道被读取了:
因为生产者和消费者也是并行的,可能存在消费者置0后还没来得及读,生产者以为管道内空无一物了,将其中的数据覆盖掉,所以要等消费完成后再进行置0。