用jsp做视频网站,银川哪家网络公司做网站做得好,网站收录查询,做网站可以在哪儿接活目录
一、信号量相关函数
1. 创建信号量集
2. 获取信号量集
3. 等待、通知信号量集
4. 控制信号量集
二、简单进程同步
1. 创建信号量集
2. P操作
3. V操作
4. 删除信号量集
5. 测试#xff1a;
三、生产者与消费者
1. 创建、删除共享内存及信号量集
2. 单一生产…目录
一、信号量相关函数
1. 创建信号量集
2. 获取信号量集
3. 等待、通知信号量集
4. 控制信号量集
二、简单进程同步
1. 创建信号量集
2. P操作
3. V操作
4. 删除信号量集
5. 测试
三、生产者与消费者
1. 创建、删除共享内存及信号量集
2. 单一生产者和消费者
2.1. 生产者
2.1. 消费者
3. 多生产者多消费者
4. 改进
四、总结 一、信号量相关函数
1. 创建信号量集
本次实验使用信号量机制来实现进程的同步与互斥因此首先需要创建信号量集。
使用函数semget可以创建信号量集它的原型如下
int semget(key_t key, int nsems, int semflg)
其中key是创建信号量集的键nsems是信号量集中信号量量的数量
semflg是指定的选项及其权限位包含IPC_CREAT创建新的信号量集、IPC_EXCEL如果信号量集已经存在则返回错误等。
这个函数创建的是一个信号量集其中包含多个信号量可以通过函数semop来访问信号量集中的某个信号量或者使用semctl函数来对信号量进行操作一般创建数据集后都要首先使用semctl对每个信号量设置初始值。semop和semctl函数的介绍在下面。
2. 获取信号量集
一个进程创建号信号量集后另一个进程想要访问这个信号量集可以使用semget函数传入KEY值来获取该信号量集的id该函数原型如下
int semid semget(KEY, 0, 0);
上述代码只为获取一个已经存在的信号量集不需要nsems和semflg全部为0即可。获取成功会返回信号量集id如果获取失败的话会返回-1 。
3. 等待、通知信号量集
使用semop函数可以访问一个信号量集进行获取P操作和释放V操作其原型如下
int semop(int semid,struct sembuf *sops,unsigned nsops)
semid是使用semget函数获取到的信号量集的id
sops是一个sembuf类型的结构用于描述信号量的操作等待、通知等其定义如下
struct sembuf{short sem_num; // 要访问的信号量在信号量集中的索引short sem_op; // 对信号量的操作为负数是P操作正数是V操作short sem_flg; // 操作标志可以是0或IPC_NOWAIT非阻塞方式
}
nsops是指定信号量集中操作的信号量个数。
4. 控制信号量集
要对整个信号量集进行操作可以使用semctl函数其原型如下
int semctl(int semid, int semnum, int cmd, union semun arg)
semid是由semget函数返回的信号量集id
semnum是信号量在信号量集中的索引
cmd是控制命令用于对信号量执行指定的操作命令包括 IPC_STAT获取信号量集合的属性信息将结果写入指定的结构体中。 IPC_SET设置信号量集合的属性信息使用指定的结构体中的值进行设置。 GETVAL获取指定信号量的值。 SETVAL设置指定信号量的值。 GETALL: 获取信号量集合中所有信号量的值 SETALL: 设置所有信号量的值 GETPID获取最后一个执行 semop() 操作的进程 ID。 GETNCNT获取等待该信号量值增加的进程数。 GETZCNT获取等待该信号量值变为 0 的进程数。 IPC_RMID删除信号量集合。 二、简单进程同步
现在使用上面给出的函数编写几个程序实现进程同步。
信号量机制实现进程同步需要使用一些简单的信号量集操作包括创建信号量集P操作V操作删除信号量集。
1. 创建信号量集
createSem.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
using namespace std;
#define KEY 2002
int main(){int semid semget(KEY, 1, IPC_CREAT);semctl(semid, 0, SETVAL, 0);}
上面代码使用semid函数创建一个只包含一个信号量的信号量集随后使用semctl将该信号量的值初始化为0 。
2. P操作
p1.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
#define KEY 2002
using namespace std;
int main(){int semid semget(KEY, 0, 0); struct sembuf *sops new sembuf;sops-sem_num 0;sops-sem_op -1;sops-sem_flg 0;if(semop(semid, sops, 1) -1){char err[] semop;perror(err);exit(1);}cout 获取成功 endl;return 0;
}
上一个程序createSem创建完信号量集后信号量集就保存在缓冲区中此时另一个程序可以使用semget函数访问该信号量集使用同样的键值KEY就能访问到上一个进程创建的信号量集。
上述代码首先获取已经创建好的信号量集随后定义sembuf类型结构指针sops设置访问信号量索引sem_num为0信号量操作sem_op为-1p操作信号量减一操作标志为0 。接着就使用semop函数传入sops对信号量进行操作如果操作失败如信号量集不存在或信号量索引非法等就输出错误信息结束程序。如果获取成功则会执行下面的语句打印“获取成功”如果进行P操作时该信号量为0此时进行会阻塞等待其他进程释放信号为了简化问题一开始设置信号量为0此时执行p1程序一定会阻塞。
3. V操作
p2.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
#define KEY 2002
using namespace std;
int main(){int semid semget(KEY, 0, 0); struct sembuf *sops new sembuf;sops-sem_num 0;sops-sem_op 1;sops-sem_flg 0;if(semop(semid, sops, 1) -1){char err[] semop;perror(err);exit(1);}return 0;
}
上面代码与p1基本时一样的只是修改了sem_op改为1V操作信号量加1执行该程序后对应信号量会加一原本阻塞的程序就能结束等待继续执行下去。
4. 删除信号量集
deletSem.cpp
信号量集使用完毕后需要删除信号量集使用semctl函数实现控制命令选择IPC_RMID。
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
using namespace std;
#define KEY 2002
int main(){int semid semget(KEY, 0, 0);semctl(semid, 0, IPC_RMID, 0);}
5. 测试
现在可以执行上面的4个程序了执行的顺序是createSem创建信号量集、p1P操作、p2V操作、deleteSem删除信号量集
在终端执行如下命令创建各个文件并编译。 接着先执行createSem和p1程序 可以看到执行p1程序后不会输出“获取成功”也没有结束因为一开始设置信号量为0因此此时p1获取资源P操作是会阻塞的。
在这时我们再运行p2程序释放资源V操作释放后该信号量加一此时p1程序就能正常获取资源了程序会继续执行下去。 可以看到在另一个终端执行p2后p1就能正常执行下去了并打印“获取成功”。
最后不要忘了删除信号量集。 三、生产者与消费者
现在我们掌握了信号量的基本操作现在我们使用上面的函数编写程序解决生产者与消费者问题。
按照生产者和消费者的问题描述我们需要三个信号量一个是互斥信号量mutex用于实现各进程对缓冲池的互斥使用一个是empty用于表示缓冲池中空缓冲区的数量最后一个full表示缓冲池中满缓冲池的数量。在这里我们使用共享内存作为公用缓冲池在上次实验我们在共享内存中写入数据是会覆盖掉原数据的因此设置共享内存中最多只能写入一个数据相当于公用缓冲池中只有一个可用的缓冲区因此信号量empty和full最多为1二者的取值范围都是{-101}。
其实共享内存也不是不能写入多个数据它会覆盖原数据是因为获取的共享内存的地址都是首地址同一个地址重复写入数据当然会覆盖原数据了。如果是字符串的话比较难实现写入多个数据因为字符串的长度是不定的这要区分每个字符串的话比较麻烦使用\0但如果是整型数据的话是可以很容易实现写入多个数据的因为整型是定长的可以将公用缓冲池划分为多个整型大小的缓冲区每个缓冲区存储一个数据这样就能实现缓冲池有多个缓冲区了缓冲区的意义是不是这个我不太确定不过看书上空缓冲区满缓冲区的描述应该就是一个缓冲区存放一个数据这个后面再说先实现一个简单的只有一个缓冲区的生产者与消费者。
根据要求修改createSem程序修改为创建一个有3个信号量的信号量集并按照要求初始化各个信号量的值再添加创建共享内存的操作同时为创建信号量集和共享内存添加错误判断在遇到错误时打印错误信息。
1. 创建、删除共享内存及信号量集
createSem.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
#includesys/shm.h
#define KEY 2002
#define SHMKEY 2020
#define SIZE 256
using namespace std;
int main(){// 创建信号量集int semid semget(KEY, 3, IPC_CREAT);if (semid -1) {perror(Failed to create semaphore);exit(1);}// 创建互斥信号量mutex实现各进程对共享内存的互斥使用semctl(semid, 0, SETVAL, 1);// 创建信号量empty表示空缓冲区数量暂时只能覆盖写入最多为1semctl(semid, 1, SETVAL, 1);// 创建信号量full表示满缓冲区的数量同样最多只能为1初始为0semctl(semid, 2, SETVAL, 0);// 创建共享内存int shmid shmget(SHMKEY, SIZE, IPC_CREAT);if (shmid -1) {perror(Failed to create shared memory);exit(1);}}
根据需要修改deleteSem程序增加一个删除共享内存的操作同时添加错误判断。
deleteSem.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
#includesys/shm.h
#define KEY 2002
#define SHMKEY 2020
using namespace std;
int main(){int semid semget(KEY, 0, 0);if (semid -1) {perror(Failed to get semaphore);exit(1);}// 删除信号量集semctl(semid, 0, IPC_RMID, 0);int shmid shmget(SHMKEY, 0, 0);if (shmid -1) {perror(Failed to get shared memory);exit(1);}// 删除共享内存shmctl(shmid, IPC_RMID, NULL);
}
2. 单一生产者和消费者
2.1. 生产者
生产者程序生产一个产品后需要先等待共享内存中有空缓冲区接着在等待申请共享内存资源获得共享内存资源后送入产品写入数据操作完成后释放共享内存资源mutex信号量加一同时full信号量加一告知消费者现在缓冲池中已经有满缓冲区了。
流程如下 produce an item nextp; wait(empty); wait(mutex); *buffer nextp; signal(mutex); signal(full); buffer是共享内存地址
代码
producer.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/shm.h
#includesys/ipc.h
#includectime
#includeunistd.h
#define KEY 2002
#define SHMKEY 2020
using namespace std;
void simSemop(int semid, int sem_num, int sem_op);
int main(){int semid semget(KEY, 0, 0); int i 0;while(1){// 等待缓冲池中有空缓冲区waitemptysimSemop(semid, 1, -1);// 获取共享内存资源waitmutex)simSemop(semid, 0, -1);// 获取共享内存int shmid shmget(SHMKEY, 0, 0);// 连接共享内存int *shmadd (int*) shmat(shmid, NULL, 0);// 获取进程号 int pid getpid();// 写入数据进程号 *shmadd pid;shmdt(shmadd);// 释放共享内存资源signal(mutex)simSemop(semid, 0, 1);cout 生产者进程 getpid() 生产成功 i endl;// 增加满缓冲区数量signal(full) simSemop(semid, 2, 1);// 生产间隔1-9秒srand(time(NULL));int slptime rand() % 9 1;sleep(slptime);i; }
}
void simSemop(int semid, int sem_num, int sem_op){struct sembuf *sops new sembuf;sops-sem_num sem_num;sops-sem_op sem_op;sops-sem_flg 0;if(semop(semid, sops, 1) -1){perror(Semop fail);exit(1);}
}
由于要多次使用P操作和V操作因此我将P操作和V操作都封装在simSemop函数中可以直接调用该函数来简化步骤。
生产者是不断生产产品的这里的产品是生产者的进程号无限循环上面的流程上面的代码实际上是没有生产产品的过程的因此每次生产之后需要等待1到9秒来模拟生产的过程其实这个等待过程应该放在最上面而不是末尾。
2.1. 消费者
消费者等待共享内存中有满缓冲区共享内存中有满缓冲区后消费者再申请获取共享内存资源获取成功后从共享内存中取出数据最后释放共享内存资源mutex信号量加一同时empty信号量加一告知生产者现在缓冲池中已经有空缓冲区可以写入数据了。
流程如下 wait(full); wait(mutex); nextc *buffer; signal(mutex); signal(empty); consume the item in nextc; 代码
consumer.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
#includesys/shm.h
#includeunistd.h
#includectime
#define KEY 2002
#define SHMKEY 2020
using namespace std;
void simSemop(int semid, int sem_num, int sem_op);
int main(){int semid semget(KEY, 0, 0); struct sembuf *sops new sembuf;while(1){// 等待缓冲池中有满缓冲区wait(full)simSemop(semid, 2, -1);// 获取共享内存资源wait(mutex)simSemop(semid, 0, -1); int shmid shmget(SHMKEY, 0, 0);int *shmadd (int*) shmat(shmid, NULL, 0);cout 消费者进程 getpid() 获取 *shmadd endl;// 释放共享内存资源signal(mutex)simSemop(semid, 0, 1);// 增加空缓冲区数量signal(empty)simSemop(semid, 1, 1);}
}
void simSemop(int semid, int sem_num, int sem_op){struct sembuf *sops new sembuf;sops-sem_num sem_num;sops-sem_op sem_op;sops-sem_flg 0;if(semop(semid, sops, 1) -1){perror(Semop fail);exit(1);}
}
测试 首先创建信号量集和共享内存接着先运行消费者此时共享内存中没有可用的数据可用数据信号量full为0消费者进程会阻塞接着再运行生产者空间区信号量empty初始为1此时生产者可以正常运行生产产品消费者就能同步获取产品接着生产者等待1-9秒后再生产消费者阻塞等待生产者再次生产。
3. 多生产者多消费者
现在增加一点点难度运行多个生产者进程和多个消费者进程。
可以直接在生产者和消费者程序中加上创建子进程的代码在while循环之前
// 创建5个子进程
for(int i 0; i 5; i){// 创建子进程 pid_t child fork();// 如果是子进程就退出循环防止子进程也创建子进程if(child 0)break;
}
这里我创建了5个子进程一共就是6个生产者进程和6个消费者进程。
运行测试 可以看出这样的程序是有些问题的多个生产者是同时生产产品的因为time函数获取的时间是秒级的而一秒对进程来说还是太长了在一秒内足够6个进程都创建出来并且生产完毕此时6个生产者进程处在同一秒内获取的时间都是一样的而随机种子一样生成的随机数也都是一样的因此它们会等待同样长的时间之后又会同时生产产品。为了解决这个问题我们可以将使用精度更高的函数来获取时间比如clock_gettime。
改进代码
producer.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/shm.h
#includesys/ipc.h
#includectime
#includeunistd.h
#define KEY 2002
#define SHMKEY 2020
using namespace std;
void simSemop(int semid, int sem_num, int sem_op);
int main(){int semid semget(KEY, 0, 0); int i 0;// 创建5个子进程for(int i 0; i 5; i){// 创建子进程 pid_t child fork();// 如果是子进程就退出循环防止子进程也创建子进程if(child 0)break;}while(1){// 获取纳秒级时间struct timespec ts;clock_gettime(CLOCK_MONOTONIC, ts);// 以时间的纳秒部分作为随机种子srand(ts.tv_nsec);int slptime rand() % 9 1;// 生产时间sleep(slptime);// 等待缓冲池中有空缓冲区waitemptysimSemop(semid, 1, -1);// 获取共享内存资源waitmutex)simSemop(semid, 0, -1);// 获取共享内存int shmid shmget(SHMKEY, 0, 0);int *shmadd (int*) shmat(shmid, NULL, 0);// 获取进程号 int pid getpid();// 写入数据进程号 *shmadd pid;shmdt(shmadd);// 释放共享内存资源signal(mutex)simSemop(semid, 0, 1);cout 生产者进程 getpid() 生产成功 i endl;// 增加满缓冲区数量signal(full) simSemop(semid, 2, 1);i; }
}
void simSemop(int semid, int sem_num, int sem_op){struct sembuf *sops new sembuf;sops-sem_num sem_num;sops-sem_op sem_op;sops-sem_flg 0;if(semop(semid, sops, 1) -1){perror(Semop fail);exit(1);}
}
生产者代码不用更改
上面代码将随机种子改为当前时间的纳秒部分并且将睡眠的部分移到了最上面这样更贴合实际生产时间。
运行测试 可以看到现在正常了一开始运行消费者进程此时没有生产者进程在运行所有消费者进程在等待队列中等待生产者生产产品。运行生产者进程后生产者进程等待随机的时间模拟生产时间后生产出产品放入共享内存只要6个生产者进程中有一个生产出产品等待队列中最前面这里是先进先出的消费者会同步获取产品其他消费者则继续等待新产品生产者生产后需要等待随机的时间再次生产出产品如此往复。
从上面的动图中我们也能看出最先创建的消费者进程最先获取产品因为它最先等待在等待队列的最前面但父进程由于需要创建其他的进程因此父进程最后一个进入等待队列或者最后一个创建的子进程最后进入等待队列上图中就是前4个子进程 26200、26201、26202、26203 先等待然后是父进程26199最后是最后一个创建的子进程26204 。消费者进程获取完产品后会重新等待由于一开始消费者进程和生产者进程不是同时运行的在生产者运行时消费者进程已经全部创建好并且在等待了就算是同时也一样因为生产者最短也要1秒时间生产产品因此第一个取产品的消费者进程获取完后会处于第二轮获取产品的第一位其他消费者进程以此类推因此每一轮消费者等待的顺序都是一样的如此往复。
4. 改进
在上一节说过共享内存也可以有多个缓冲区写入多个数据现在我们使用有多个缓冲区的公用缓冲池解决生产者和消费者问题。
再这之前我们先来简单了解一下内存地址如果对指针很了解的话应该就很容易理解其实指针也可以看成是地址或者更应该说指针变量存放的是地址指针是一个变量它是有类型的地址加一就是实际的加一指针加一却是加一个类型的长度比如整型4个字节整型指针加一就是地址加4。
如果使用shmat函数获取共享内存的指针时将其强制转化为整型那么这个指针就是整型的指针每次移动的长度就一个整型的长度相当于将共享内存分为了多个区每个区都可以存放一个整型数据。
假设一个共享内存大小是20字节使用整型指针访问共享内存就相当于共享内存中有5个区每个区4个字节一般在linux中int类型长度是4个字节指针每次加一地址就会增加一个区的长度4bytes。如下图 #includeiostream
using namespace std;
int main(){int a[5];int* p a;for(int i 0; i 5; i){*(p i) i;}for(int i 0; i 5; i){cout *(p i) ;}cout endl;
}
上面这个代码就是一个简单的示例用整型数组来代替共享内存整型数组其实就是一块连续的内存地址使用一个整型指针访问内存中的每个区为其赋值0到4 。 之后再使用指针访问每个区输出该区的数据。
运行结果 地址空间及地址存放数据如下 要是不理解的话就把它当成是一个整型数组指针p就相当于整型数组变量ap3就是a3 *(p3)取p3地址上的值就是a[3] 实际上数组变量a就是个指针。
指针是用来存放地址的使用“*”操作符可以取指针所指向的地址中的值为指针指定类型就能规定指针每次偏移时地址移动的长度以及取数据时最多长的数据。地址实际并没有改变但使用不同类型的指针访问地址得到的数据会不一样如果乱用指针的话可能会读取到乱码使用指针访问地址地址就相当于被分成了多个区每个区存放一个指定类型int、char等的数据。在存放数据和取出数据时用的指针类型最好要一样乱用指针的后果可是很严重的。
数组其实跟指针是一样的数组就是申请了一块连续的内存然后按照数组的类型“划分”内存按照下标取对应区内的数据数组变量a实际上就是一个指针数组变量可以当成指针用*a访问元素也可以使用下标a[0]。
既如此我们就可以使用整型指针将共享内存 “划分“ 为多个区每个区存放一个整型数据。我们需要两个变量来存储in值和out值两个值是指针的偏移量每个生产者写入数据的地址都是pin写入后将in的值加一每个消费者取数据的地址都是pout读取后将out加一in和out的值我们可以存放在共享内存的最后两个”整型区“可用的区数量要减少2防止误访问到in和out。
创建一个大小为6个整型大小的共享内存其中前五个整型地址空间为空缓冲区最后两个存放in和out。
首先需要更改createSem程序将共享内存的大小改为28共可以存储7个int类型数据前5个作为缓冲区剩下最后两个分别存储in和out的值in和out初始值都是0。
createSem.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
#includesys/shm.h
#define KEY 2002
#define SHMKEY 2020
#define SIZE 28
using namespace std;
int main(){// 创建信号量集int semid semget(KEY, 3, IPC_CREAT);if (semid -1) {perror(Failed to create semaphore);exit(1);}// 创建互斥信号量mutex实现各进程对共享内存的互斥使用semctl(semid, 0, SETVAL, 1);// 缓冲区数量最后两个存放in和out不算入缓存区数量int bufferNum (SIZE / sizeof(int) - 2);// 创建信号量empty表示空缓冲区数量初始为缓冲区数量semctl(semid, 1, SETVAL, bufferNum);// 创建信号量full表示满缓冲区的数量初始为0semctl(semid, 2, SETVAL, 0);// 创建共享内存int shmid shmget(SHMKEY, SIZE, IPC_CREAT);if (shmid -1) {perror(Failed to create shared memory);exit(1);}int* shmadd (int*) shmat(shmid, 0, 0);// 将缓冲区初始都设为空缓冲区赋值-1for(int i 0; i bufferNum; i)*(shmadd i) -1;// 倒数第二位存放in的值初始为0*(shmadd bufferNum) 0;// 最后一位存放out的值初始为0*(shmadd bufferNum 1) 0;}
生产者程序生产一个产品后需要先等待共享内存中有空缓冲区接着在等待申请共享内存资源获得共享内存资源后首先获取共享内存首地址然后访问共享内存倒数第二个缓冲区获取指针偏移量in根据in在对应的地址中写入数据写完后再将倒数第二个缓冲区中的in值加一。操作完成后释放共享内存资源mutex信号量加一同时full信号量加一告知消费者现在缓冲区中已经有满缓冲区了。
producer.cpp
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/shm.h
#includesys/ipc.h
#includectime
#includeunistd.h
#define KEY 2002
#define SHMKEY 2020
#define SIZE 28
using namespace std;
void simSemop(int semid, int sem_num, int sem_op);
int main(){int semid semget(KEY, 0, 0); int i 0;// 创建5个子进程for(int i 0; i 5; i){// 创建子进程 pid_t child fork();// 如果是子进程就退出循环防止子进程也创建子进程if(child 0)break;}while(1){// 获取纳秒级时间struct timespec ts;clock_gettime(CLOCK_MONOTONIC, ts);// 以时间的纳秒部分作为随机种子srand(ts.tv_nsec);int slptime rand() % 9 1;// 生产时间sleep(slptime);// 等待缓冲池中有空缓冲区waitemptysimSemop(semid, 1, -1);// 获取共享内存资源waitmutex)simSemop(semid, 0, -1);// 获取共享内存int shmid shmget(SHMKEY, 0, 0);int* shmadd (int*) shmat(shmid, NULL, 0);int bufferNum SIZE / sizeof(int) - 2;// 获取in的值in所在的位置是倒数第二个整型大小的地址空间int in *(shmadd bufferNum);// 获取进程号int pid getpid();// 写入数据进程号 *(shmadd in) pid;// in值加一需要mod区数量使其处于缓存池的最大区数量范围内*(shmadd bufferNum) (in 1) % (bufferNum);shmdt(shmadd);// 释放共享内存资源signal(mutex)simSemop(semid, 0, 1);cout 生产者进程 getpid() 生产成功 i endl;// 增加满缓冲区数量signal(full) simSemop(semid, 2, 1);i; }
}
void simSemop(int semid, int sem_num, int sem_op){struct sembuf *sops new sembuf;sops-sem_num sem_num;sops-sem_op sem_op;sops-sem_flg 0;if(semop(semid, sops, 1) -1){perror(Semop fail);exit(1);}
}消费者等待共享内存中有满缓冲区共享内存中有数据后消费者在申请获取共享内存资源获得共享内存资源后首先获取共享内存首地址然后访问共享内存最后一个缓冲区获取指针偏移量out根据out在对应的地址中读取数据读取完后再将最后一个缓冲区中的out值加一。最后释放共享内存资源mutex信号量加一同时empty信号量加一告知生产者现在缓冲池中已经有空缓冲区可以写入数据了。
#includeiostream
#includesys/sem.h
#includesys/types.h
#includesys/ipc.h
#includesys/shm.h
#includeunistd.h
#includectime
#define KEY 2002
#define SHMKEY 2020
#define SIZE 28
using namespace std;
void simSemop(int semid, int sem_num, int sem_op);
int main(){int semid semget(KEY, 0, 0); struct sembuf *sops new sembuf;// 创建5个子进程for(int i 0; i 5; i){// 创建子进程 pid_t child fork();// 如果是子进程就退出循环防止子进程也创建子进程if(child 0)break;}while(1){// 等待缓冲池中有满缓冲区wait(full)simSemop(semid, 2, -1);// 获取共享内存资源wait(mutex)simSemop(semid, 0, -1);int shmid shmget(SHMKEY, 0, 0);int *shmadd (int*) shmat(shmid, NULL, 0);// 缓冲区数量int bufferNum SIZE / sizeof(int) - 2;// 获取out值in所在的位置是最后一个整型大小的地址空间 int out *(shmadd bufferNum 1);cout 消费者进程 getpid() 获取 *(shmadd out) endl;// out值加一%bufferNum 是为了保证值的范围在0到bufferNum - 1之间*(shmadd bufferNum 1) (out 1) % bufferNum;// 释放共享内存资源signal(mutex)simSemop(semid, 0, 1);// 增加空缓冲区signal(empty)simSemop(semid, 1, 1);}
}
void simSemop(int semid, int sem_num, int sem_op){struct sembuf *sops new sembuf;sops-sem_num sem_num;sops-sem_op sem_op;sops-sem_flg 0;if(semop(semid, sops, 1) -1){perror(Semop fail);exit(1);}
}运行结果 如上图这次一开始我们先运行生产者观察一下生产者因缓冲池中没有空缓冲区而阻塞可以看到在生产了5个产品后6个生产者进程就被阻塞了没有一个能继续生产产品接着在运行消费者在5个消费者进程取出5个产品后缓冲池立即多出5个空缓冲区立马有5个生产者生产出产品消费者同步获取产品接着就是生产者一个个生产产品消费者同步获取产品如此往复。
四、总结
生产者消费者问题主要要解决的就是进程之间同步的问题以防止进程之间无序争夺资源造成系统混乱。
生产者和消费者共用一个缓冲池生产者写入数据消费者取出数据为了防止数据错误同一个时间只能有一个进程使用缓冲池实现进程互斥访问缓冲池消费者要在生产者生产完产品缓冲池中有产品后再去申请获取缓冲池资源生产者在缓冲池满了后要在消费者取出产品后再申请缓冲池资源将产品放入缓冲池。
使用信号量机制实现上面的要求就需要3个信号量一个是缓冲池的资源数量最大为1实现互斥访问缓冲池一个是缓冲池中空缓冲区数量信号量最后一个是缓冲池满缓冲区数量信号初始为0生产者首先等待缓冲池空缓冲区信号量再申请缓冲池资源生产完后释放缓冲池资源增加缓冲池中满缓冲区数量信号量消费者先等待缓冲池中满缓冲区信号量再申请缓冲池资源生产完后释放缓冲池资源增加缓冲池空缓冲区数量信号量。
生产者和消费者写入数据和取出数据都是按照顺序进行的各个生产者进程按照in的值按顺序往下写入数据各个消费者进程按照out的值按顺序从缓冲区中取出数据。这样不会出现生产者写入到满缓冲区的情况也不会出现生产者从空缓冲区取出数据的情况因为生产者一开始缓冲区全是空的生产的时候按照顺序一个个写入数据就不会写入到满缓冲区中生产者在前面一直往前写入数据时消费者在生产者后面取数据此时消费者访问的地址中都是放满数据的消费者只要一直在生产者后面就不会出现访问到空缓冲区的情况而信号量机制保证了这一点只有生产者生产完后消费者才能同步获取数据不会出现消费者在生产者之前访问共享内存。将out和in的值mod缓冲区数量就能实现在共享内存中循环写入和读取数据。
此次实验还是有点难度的至少比上次实验难不少这次实验没有例子我从最基本的步骤开始从简单的进程同步到只有一个缓冲区的生产者和消费者同步到最后完整的生产者和消费者同步一开始还以为共享内存不好划分缓冲区后面想了想想起了c语言了指针用它就能实现这个操作但是如果是字符串的话也还是不好搞的。