网上流传的有个版本会有下面所述的问题,这个是自己的作业。
实验二 Linux 进程同步
1. 关键问题
1) 实现子进程在父进程写数据之后才开始读取数据
2) 实现在所有子进程读取数据之后父进程才开始写数据
3) 在读取的时候不互斥缓冲区,实现数据的并发读取
2. 设计思路
本程序总体设计思想为,一个父进程(生产者)写数据,任意数量个(超过 3 个)子进程(消费者)可以并发地读取数据。
对于第一个问题,我们使用名为 semread 的信号量。它的初始值为 0 ,子进程(消费者)在开始运行时执行 P(1) ,把信号量的值减 1 ,所以子进程会进入等待。待父进程(生产者)写入数据后进行 V(3) 操作,把信号量的值加上 3 。然后 3 个子进程开始运行。
对于第二个问题,我们使用名为 semwrite 的信号量。在父进程对 semread 操作唤醒所有等待的子进程之后。父进程对 semwrite 执行 P(3) 操作, semwrite 的初始值为 0 ,所以父进程会进入休眠状态。待三个子进程读取数据之后。每个子进程都对 semwrite 执行 V(1) 操作。然后父进程就会被唤醒。
上面,似乎已经解决了问题。但是,其实没有。如果某个子进程处理速度很快的话。那么它可能会两次或更多次成功对 semread 执行 P ( 1 )操作,然后对 semwrite 执行 V(1) 操作,那么,另外一个子进程在没有读取数据的情况下,父进程被唤醒。因此我们引定第三个信号量的定义,一个初值为 0 名字 semchild 的信号量。在子进程对 semwrite 进行 V(1) 操作后,还要执行 semchild 的 P ( 1 )操作,由于 semchild 的初始值为 0 。所以子进程会进入休眠状态。不会再有机会有多次成功对 semread 的可能。由于是先进行对 semwrite 进行 V(1) 。所以,父进程会从对 semwrite 的 P(3) 操作中唤醒。这时三个子进程休眠在对 semchlid 的 P(1) 操作中。父进程对 semchild 进行 V(3) 操作,所有子进程被唤醒,子进程又进入了对 semread 的等中。
3. 实现的关键代码
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <sys/sem.h>
int semwrite; /*can write*/
int semread; /*can read*/
int semchild; /*for child */
int *data; /*for share data*/
struct sembuf p1, p3; /*pn n is sub num*/
struct sembuf v1, v3; /*vn n is add num*/
int customer_read()
{
while(1)
{
semop(semread, &p1, 1);
if (*data > 0) {
printf("child process %d:parent data %d/n",
getpid(), *data);
semop(semwrite, &v1, 1);
semop(semchild, &p1, 1);
} else {
printf("chlid process %d:exit signal recive/n",
getpid());
semop(semwrite, &v1, 1);
exit(0);
}
}
}
int fork_child()
{
pid_t ret = fork();
if (ret == 0) {
customer_read();
} else if (ret > 0) {
return ret;
} else {
perror("fork error");
exit(ret);
}
}
int main()
{
int semarg;
data = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
semwrite = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
semread = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
semchild = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
if(semwrite < 0 && semread < 0) {
perror("semget error");
return 0;
}
semarg = 0;
if (semctl(semwrite, 0, SETVAL, semarg) == -1) {
perror("semctl error");
return 0;
}
if (semctl(semread, 0, SETVAL, semarg) == -1) {
perror("semctl error");
return 0;
}
v1.sem_num = 0;
v1.sem_op = 1;
v1.sem_flg = 0; /*it's form man semop: If sem_op is a positive integer and the calling process has alter permission, the value of sem_op shall be added to semval and, if (sem_flg &SEM_UNDO) is non-zero, the value of sem_op shall be subtracted from the calling process' semadj value for the specified semaphore*/
v3.sem_num = 0;
v3.sem_op = 3;
v3.sem_flg = 0;
p1.sem_num = 0;
p1.sem_op = -1;
p1.sem_flg = 0;
p3.sem_num = 0;
p3.sem_op = -3;
p3.sem_flg = 0;
fork_child();
fork_child();
fork_child();
int i;
for (i = 0; i < 5; i++)
{
srand(time(NULL));
*data = rand();
semop(semread, &v3, 1);
semop(semwrite, &p3, 1);
semop(semchild, &v3, 1);
sleep(1);
}
*data = -1;
semop(semread, &v3, 1);
wait();
wait();
wait();
printf("all child process exit/n");
}
4. 程序运行结果
5. 总结及进一步改善建议
通过这次实验,深刻体会到了并发和同步编程的困难。开始想只使用两个信号来实现这个实验。但是到最后,还是用了三个。但是这个程序是完美的。不会受到进程调度的影响。改善的就是,使用mmap 映射内存后要调用munmap 来取消映射。