Linux进程互斥
(二)模拟生产者-消费者的示例程序
本示例主要体现进程间的直接制约关系,由于使用共享存储区,也存在间接制约关系。进程分为服务进程和客户进程,服务进程只有一个,作为消费者,在每次客户进程改变共享存储区内容时显示其数值。各客户进程作为生产者,如果共享存储区内容已经显示(被消费),可以接收用户从键盘输入的整数,放在共享存储区。
编译后执行,第一个进程实例将作为服务进程,提示:
ACT CONSUMER!!! To end, try Ctrl+C or use kill.
服务进程一直循环执行,直到用户按Ctrl+C终止执行,或使用kill命令杀死服务进程。
其他进程实例作为客户进程,提示:
Act as producer. To end, input 0 when prompted.
客户进程一直循环执行,直到用户输入0。
示例程序代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define MY_SHMKEY
10071500 // need to change
#define MY_SEMKEY
10071500 // need to change
void sigend(int);
int shmid, semid;
int main(void)
{
int
*shmptr, semval, local;
struct
sembuf semopbuf;
if((shmid=shmget(MY_SHMKEY, sizeof(int), IPC_CREAT|IPC_EXCL|0666))
< 0)
{
shmid=shmget(MY_SHMKEY, sizeof(int), 0666);
semid=semget(MY_SEMKEY, 2, 0666);
shmptr=(int *)shmat(shmid, 0, 0);
printf("Act as producer. To end, input 0 when prompted.\n\n");
printf("Input a number:\n");
scanf("%d", &local);
while( local )
{
semopbuf.sem_num=0;
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
semop(semid, &semopbuf,
1);
*shmptr =
local;
semopbuf.sem_num=1;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
semop(semid, &semopbuf,
1);
printf("Input a number:\n");
scanf("%d", &local);
}
}
else
{
semid=semget(MY_SEMKEY, 2, IPC_CREAT|0666);
shmptr=(int *)shmat(shmid, 0, 0);
semval=1;
semctl(semid, 0, SETVAL,
semval);
semval=0;
semctl(semid, 1, SETVAL,
semval);
signal(SIGINT, sigend);
signal(SIGTERM, sigend);
printf("ACT CONSUMER!!! To end, try Ctrl+C or use kill.\n\n");
while(1)
{
semopbuf.sem_num=1;
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
semop(semid, &semopbuf,
1);
printf("Shared memory set to %d\n", *shmptr);
semopbuf.sem_num=0;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
semop(semid, &semopbuf,
1);
}
}
}
void sigend(int sig)
{
shmctl(shmid, IPC_RMID, 0);
semctl(semid, IPC_RMID, 0);
exit(0);
}
运行结果:
服务端:消费者
客户端:生产者
二者通过公共存储区进行通信。
第一次执行该程序,创建公共存储区成功,
(shmid=shmget(MY_SHMKEY, sizeof(int),
IPC_CREAT|IPC_EXCL|0666))<0不成立,则进入else分支,即,创建了一个服务进程:消费者。因公共存储区为空,故消费者进程处于等待状态。
第二次执行该程序,创建公共存储区失败,条件成立,则进入if分支,即:创建一个客户进程:生产者。生产者提示输入一个整数,输入后,生产者将该整数保存到公共存储区,此时消费者会唤醒进行消费,把数据读出并输出。
1、模拟生产者-消费者
实现相应的示例程序功能,记录执行结果;改造该程序,取消所有的同步机制,记录执行结果,看是否能观察到程序出现错误情况;进一步改造程序,使错误情况易于观察到;记录执行情况并进行分析。
分析:
书上通过wait(full) signal(empty) 和
wait(empty) signal(full)
来实现同步机制。
1》改造该程序,取消所有的同步机制:即去掉实现wait(full) signal(empty) 和 wait(empty) signal(full)的语句semop(semid, &semopbuf,
1);和semop(semid, &semopbuf, 1);。代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define MY_SHMKEY
10071500 // need to change
#define MY_SEMKEY
10071500 // need to change
void sigend(int);
int shmid, semid;
int main(void)
{
int *shmptr,
semval, local;
struct
sembuf semopbuf;
if((shmid=shmget(MY_SHMKEY, sizeof(int), IPC_CREAT|IPC_EXCL|0666))
< 0)
{
//获得共享存储区首地址
shmid=shmget(MY_SHMKEY, sizeof(int), 0666);
//跟信号量集,建立联系
semid=semget(MY_SEMKEY, 2, 0666);
//把一个共享存储区附接到进程内存空间;
shmptr=(int *)shmat(shmid, 0, 0);
//输出提示
printf("Act as producer. To end, input 0 when prompted.\n\n");
//输入整数
printf("Input a number:\n");
scanf("%d", &local);
//当输入整数为时结束循环
while( local )
{
semopbuf.sem_num=0;
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作,实现生产者、消费者的同步
//semop(semid, &semopbuf,
1);
*shmptr = local;
semopbuf.sem_num=1;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作;
//semop(semid, &semopbuf,
1);
//输入整数
printf("Input a number:\n");
scanf("%d", &local);
}
}
else
{
//建立一个信号量集
semid=semget(MY_SEMKEY, 2, IPC_CREAT|0666);
//把一个共享存储区附接到进程内存空间
shmptr=(int *)shmat(shmid, 0, 0);
//操纵一个信号量集,包括赋初值
semval=1;
semctl(semid, 0, SETVAL,
semval);
semval=0;
semctl(semid, 1, SETVAL,
semval);
//设置对信号的处理方式或处理过程
signal(SIGINT, sigend);
signal(SIGTERM, sigend);
//输出提示
printf("ACT CONSUMER!!! To end, try Ctrl+C or use kill.\n\n");
while(1)
{
semopbuf.sem_num=1;
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作
//semop(semid, &semopbuf,
1);
printf("Shared memory set to %d\n", *shmptr);
semopbuf.sem_num=0;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作
//semop(semid, &semopbuf,
1);
}
}
}
void sigend(int sig)
{
shmctl(shmid, IPC_RMID, 0);
semctl(semid, IPC_RMID, 0);
exit(0);
}
运行结果:
观察到程序出现错误情况:
消费者会一直从共享存储区中读数据local。
Local在共享存储区,系统默认给予赋值为0.
第一次执行,创建共享存储区成功,进入else分支,打开了一个消费者进程,因为同步控制,消费者会一直进行消费,即读共享存储区数据local的值然后一直输出“Shared
memory set to 0”。
第二次执行该程序,创建共享存储区失败,进入if分支,打开一个生产者进程,因无同步控制,当输入整数1时,local=1,则消费者进程会一直输出“Shared
memory set to 1”。
即,生产者没生产时,消费者已经开始消费了。并且生产者生产一个,而消费者消费多了。故,程序出现错误。
2》进一步改造程序,使错误情况易于观察到;记录执行情况并进行分析?
进一步改造程序,即在消费者进程中添加“sleep(5);”代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define MY_SHMKEY
10071500 // need to change
#define MY_SEMKEY
10071500 // need to change
void sigend(int);
int shmid, semid;
int main(void)
{
int *shmptr,
semval, local;
struct
sembuf semopbuf;
//创建一个共享存储区名为MY_SHMKEY,长度为sizeof(int)
if((shmid=shmget(MY_SHMKEY, sizeof(int), IPC_CREAT|IPC_EXCL|0666))
< 0)
{
shmid=shmget(MY_SHMKEY, sizeof(int), 0666);
//跟信号量集,建立联系
semid=semget(MY_SEMKEY, 2, 0666);
//共享存储区(Share Memory)通信机制
//可使若干进程共享主存中的某一个区域,且使该区域出现(映射)在多个进程的虚地址空间中。
//当进程间欲利用共享存储区进行通信时,必须先在主存中建立一共享存储区,
//然后将它附接到自己的虚地址空间上。
//故,从逻辑上将一个共享存储区附接到进程的虚拟地址空间上
shmptr=(int *)shmat(shmid, 0, 0);
//输出提示
printf("Act as producer. To end, input 0 when prompted.\n\n");
//输入整数
printf("Input a number:\n");
scanf("%d", &local);
//当输入整数为时结束循环
while( local )
{
semopbuf.sem_num=0;
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作,实现生产者、消费者的同步
//semop(semid, &semopbuf,
1);
*shmptr = local;
semopbuf.sem_num=1;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作;
//semop(semid, &semopbuf,
1);
//输入整数
printf("Input a number:\n");
scanf("%d", &local);
}
}
else
{
//建立一个信号量集
semid=semget(MY_SEMKEY, 2, IPC_CREAT|0666);
//把一个共享存储区附接到进程内存空间
shmptr=(int *)shmat(shmid, 0, 0);
//操纵一个信号量集,包括赋初值
semval=1;
semctl(semid, 0, SETVAL,
semval);
semval=0;
semctl(semid, 1, SETVAL,
semval);
//设置对信号的处理方式或处理过程
signal(SIGINT, sigend);
signal(SIGTERM, sigend);
//输出提示
printf("ACT CONSUMER!!! To end, try Ctrl+C or use kill.\n\n");
while(1)
{
semopbuf.sem_num=1;
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作
//semop(semid, &semopbuf,
1);
sleep(5);
printf("Shared memory set to %d\n", *shmptr);
semopbuf.sem_num=0;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作
//semop(semid, &semopbuf,
1);
}
}
}
void sigend(int sig)
{
//共享存储区的控制,对其状态信息进行读取和修改
shmctl(shmid, IPC_RMID, 0);
semctl(semid, IPC_RMID, 0);
exit(0);
}
结果:
消费者进程:
生产者进程:
分析:
消费者与生产者并发执行,会出现错误:生产者,生产出2,3,4,5,即共享存储区中变量shmid值依次为2,3,4,5。由于在消费者进程读该变量值之前加了sleep(5),消费者睡醒后,shmid=5,故产品2,3,4丢失了。可见,消费者-生产者中的同步机制非常重要,不能去掉。
自己补充:
3》只把生产者中的semop(semid, &semopbuf,
1);和semop(semid, &semopbuf, 1);去掉。代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define MY_SHMKEY
10071500 // need to change
#define MY_SEMKEY
10071500 // need to change
void sigend(int);
int shmid, semid;
int main(void)
{
int *shmptr,
semval, local;
struct
sembuf semopbuf;
if((shmid=shmget(MY_SHMKEY, sizeof(int), IPC_CREAT|IPC_EXCL|0666))
< 0)
{
//获得共享存储区首地址
shmid=shmget(MY_SHMKEY, sizeof(int), 0666);
//跟信号量集,建立联系
semid=semget(MY_SEMKEY, 2, 0666);
//把一个共享存储区附接到进程内存空间;
shmptr=(int *)shmat(shmid, 0, 0);
//输出提示
printf("Act as producer. To end, input 0 when prompted.\n\n");
//输入整数
printf("Input a number:\n");
scanf("%d", &local);
//当输入整数为时结束循环
while( local )
{
semopbuf.sem_num=0;
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作,实现生产者、消费者的同步
//semop(semid, &semopbuf,
1);
*shmptr = local;
semopbuf.sem_num=1;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作;
//semop(semid, &semopbuf,
1);
//输入整数
printf("Input a number:\n");
scanf("%d", &local);
}
}
else
{
//建立一个信号量集
semid=semget(MY_SEMKEY, 2, IPC_CREAT|0666);
//把一个共享存储区附接到进程内存空间
shmptr=(int *)shmat(shmid, 0, 0);
//操纵一个信号量集,包括赋初值
semval=1;
semctl(semid, 0, SETVAL,
semval);
semval=0;
semctl(semid, 1, SETVAL,
semval);
//设置对信号的处理方式或处理过程
signal(SIGINT, sigend);
signal(SIGTERM, sigend);
//输出提示
printf("ACT CONSUMER!!! To end, try Ctrl+C or use kill.\n\n");
while(1)
{
semopbuf.sem_num=1;
semopbuf.sem_op=-1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作
semop(semid, &semopbuf, 1);
printf("Shared memory set to %d\n", *shmptr);
semopbuf.sem_num=0;
semopbuf.sem_op=1;
semopbuf.sem_flg=SEM_UNDO;
//对信号量集进行wait和signal操作
semop(semid, &semopbuf, 1);
}
}
}
void sigend(int sig)
{
shmctl(shmid, IPC_RMID, 0);
semctl(semid, IPC_RMID, 0);
exit(0);
}
分析:
则也出现错误:生产者生产,而消费者却一直不消费。
因为生产者进程中的semop(semid, &semopbuf,
1);和semop(semid, &semopbuf,
1);被去掉后,信号量的变化在消费者看来一直没变化,即共享存储区可用资源数量一直为0,故消费者一直处理等待状态,消费者进程出了输出提示该进程为消费者进程语句外,不进行任何操作。