linux生产者消费者进程,Linux生产者与消费者的问题实现

#include#define BUFFER_SIZE 10 /* 公用环形缓冲区的大小 */

#define PRODUCER_NUM 5 /* 生产者进程的个数 */

#define CONSUMER_NUM 5 /* 消费者进程的个数 */

#define RWRWRW S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH /* 设定创建的文件的访问权限为:用户、组用户和其他用户都可读可写 */

int *pwrite, *pread; /* 即上面提到的out和in指针,分别用来指示当前第一个可读和可写的缓冲区的下标*/

int value_read, value_write; /* 分别用来保存读到的值和要写的值 */

int *shared_buffer; /* 公用环形缓冲区指针,为了公用,这个指针将指向一个共享内存的数组 */

sem_t *full, *empty; /* 分别指向full和empty这两个信号量的指针,同样地,为了公用,这两个指针指向的信号量在共享内存中实现 */

sem_t *mutex_producer, *mutex_consumer; /* 分别用来互斥生产者间以及消费者间的操作 */

extern int random_generator(int start, int end); /* 用来生成start~end间(包括start和end,是离散闭区间)的随机数 */

void producer(void); /* 生产者所执行的代码 */

void consumer(void); /* 消费者所执行的代码 */

int process_create(pid_t *pid_new, void (*routine)(void)); /* 生成子进程函数。这个函数的接口类似pthread_creat,pid_new用来保存新的子进程的pid;routine是一个函数指针,指向子进程的执行函数 */

int main(void)

{

int i;

int fd;

pid_t pid;

pid_t pid_producer[PRODUCER_NUM], pid_consumer[CONSUMER_NUM]; /* 这两个数组分别用来保存生产者进程和消费者进程的pid */

void *ptr;

int length;

/* 初始化 */

value_read = 0;

value_write = 0;

/* 计算共享内存的长度 */

length = 4*sizeof(sem_t) + (BUFFER_SIZE + 2)*sizeof(int);

/* shm_open是一个POSIX函数,用来打开或创建一个与“/shm”关联的共享内存区 */

if((fd = shm_open("/shm", O_RDWR | O_CREAT, RWRWRW)) == -1)

{

err_exit("shm_open error"); /* 出错提示,可用简单的printf或fprintf代替 */

}

if(ftruncate(fd, length) == -1) /* 截短共享内存的长度到我们所需要的长度 */

{

err_exit("ftruncate error");

}

if((ptr = mmap(0, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) /* 将共享内存(内核中的一个区域)映射到进程地址空间 */

{

err_exit("mmap error");

}

/* 共享内存的变量布局依次为:full, empty, mutex_producer, mutex_consumer, pwrite, pread, shared_buffer */

full = (sem_t *)ptr;

empty =((sem_t *)ptr) + 1;

mutex_producer = ((sem_t *)ptr) + 2;

mutex_consumer = ((sem_t *)ptr) + 3;

pwrite = (int *)(((sem_t *)ptr) + 4);

pread = (int *)(((sem_t *)ptr) + 4) + 1;

shared_buffer =(int *)(((sem_t *)ptr) + 4 ) + 2 ;

/* 初始化 */

sem_init(full, 1, 0); /* 初始化full为0,且进程间共享 */

sem_init(empty, 1, BUFFER_SIZE); /* 初始化empty为BUFFER_SIZE,且进程间共享 */

sem_init(mutex_producer, 1, 1); /* 初始化mutex_producer为1,且进程间共享,用来互斥生产者间的操作 */

sem_init(mutex_consumer, 1, 1); /* 初始化mutex_consumer为1,且进程间共享,用来互斥消费者间的操作 */

*pwrite = 0; /* 初始化写指针为0,即从第一个缓冲区开始写 */

*pread = 0; /* 初始化读指针为0,即从第一个缓冲区开始读(当然必须在生产者放入了产品后才能读) */

for(i = 0; i < PRODUCER_NUM; i++) /* 生成生产者进程 */

{

if(process_create(&pid_producer[i], producer) != 0) /* 生产者进程的执行函数为producer */

{

/* kill(0, signum) */ /* 生成失败,尚无好的处理办法 */

}

}

for(i = 0; i < CONSUMER_NUM; i++) /* 生成消费者进程 */

{

if(process_create(&pid_consumer[i], consumer) != 0) * 消费者进程的执行函数为consumer */

{

/* kill(0, signum) */ /* 生成失败,尚无好的处理办法 */

}

}

for(i = 0; i < PRODUCER_NUM + CONSUMER_NUM; i++) /* wait处理,避免僵尸进程(zombie) */

{

waitpid(0, NULL, 0);

}

shm_unlink("/shm"); /* 父进程是最后退出的,所以在他这里删除共享内存区 */

return 0;

}

/* 生产者写10次后退出 */

void producer(void)

{

while(value_write < 10) /* 判定退出条件 */

{

sem_wait(empty); /* 是否有空缓冲区,有则占有,无则被挂起,是原子操作 */

sleep(random_generator(1, 5)); /* 休眠一段随机的时间(1s~5s,包括端点) */

sem_wait(mutex_producer); /* 获取互斥量,用来访问pwrite操作 */

value_write++;

shared_buffer[*pwrite] = value_write; /* 注意互斥区操作应尽可能少,把这个语句和后面的打印语句放到互斥区里面,是为了更准确的查看测试结果(如果不放到互斥区,则打印的顺序是不确定的) */

printf("in pid: %ld, write %5d to position %5d\n", (long)getpid(), value_write, *pwrite+1);

*pwrite= (*pwrite+1)%BUFFER_SIZE; /* 修改写指针 */

sem_post(mutex_producer); /* 释放互斥量 */

sem_post(full); /* 写完一个缓冲区,释放信号量full(值加1) */

}

}

/* 消费者写10次后退出 */

void consumer(void)

{

while(value_read < 10) /* 判定退出条件 */

{

sem_wait(full); /* 是否有可读的缓冲区,有则占有,无则被挂起,是原子操作 */

sleep(random_generator(1, 5)); /* 休眠一段随机的时间(1s~5s,包括端点) */

sem_wait(mutex_consumer); /* 获取互斥量,用来访问pread */

value_read = shared_buffer[*pread]; /* 注意互斥区操作应尽可能少,把这个语句和后面的打印语句放到互斥区里面,是为了更准确的查看测试结果(如果不放到互斥区,则打印的顺序是不确定的) */

printf("in pid: %d, read %5d from position %5d\n", (long)getpid(), value_read, *pread+1);

*pread= (*pread+1)%BUFFER_SIZE; /* 修改读指针 */

sem_post(mutex_consumer); /* 释放互斥量 */

sem_post(empty); /* 读完一个缓冲区,释放信号量empty(值加1) */

}

}

/* 生成子进程函数。这个函数的接口类似pthread_creat,pid_new用来保存新的子进程的pid;routine是一个函数指针,指向子进程的执行函数 */

int process_create(pid_t *pid_new, void (*routine)(void))

{

pid_t pid;

switch(pid = fork())

{

case -1:

return errno;

break;

case 0: /* 子进程执行完routine后退出 */

routine();

exit(0);

break;

default:

*pid_new = pid;

return 0;

break;

}

}

/********************************random_generator****************************

**since all the random number can be normalized to:                         *

** from start to end, not include start and end.                           *

**   where end>start>0 && start and end are integers                        *

******************************************************************************/

int random_generator(int start, int end)

{

int num;

struct timeval seed;

gettimeofday(&seed, NULL); /* 获取当前时间 */

srand(seed.tv_usec); /* 以当前时间的微秒值作随机种子 */

num = start + (int)((float)(end-start+1)*(rand()/(float)(RAND_MAX + 1.0))); /* 生成start~end间的随机数 */

return num;

}

同样地,保证各文件在同一文件夹中,error_plp.h和error_plp.c请参看第1部分的代码。在shell下输入“gcc –o producer_consumer_n producer_consumer_n.c error_plp.c -lrt”生成可执行文件。

关于这个程序中的同步和互斥操作,很多网上资料都说必须先同步再互斥,否则会有死锁,我认为这是错的。事实上,生产者中先同步再互斥,而消费者先互斥再同步,或反之;以及生产者和消费者都先互斥再同步这几种情况都不会死锁,因为它们间并没有交叉关系,就更不可能形成死锁环。之所以先同步,再互斥,是为了更好的并发性:并发性的瓶颈是互斥区,先同步再互斥,使得互斥区代码更短。

同步和互斥的进一步讨论,将在后面的几篇文章中陆续给出,敬请多关注^_^

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值