#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”生成可执行文件。
关于这个程序中的同步和互斥操作,很多网上资料都说必须先同步再互斥,否则会有死锁,我认为这是错的。事实上,生产者中先同步再互斥,而消费者先互斥再同步,或反之;以及生产者和消费者都先互斥再同步这几种情况都不会死锁,因为它们间并没有交叉关系,就更不可能形成死锁环。之所以先同步,再互斥,是为了更好的并发性:并发性的瓶颈是互斥区,先同步再互斥,使得互斥区代码更短。
同步和互斥的进一步讨论,将在后面的几篇文章中陆续给出,敬请多关注^_^