首页 > linux进程间通信:system V 信号量 生产者和消费者模型编程案例

linux进程间通信:system V 信号量 生产者和消费者模型编程案例

生产者和消费者模型:

  • 有若干个缓冲区,生产者不断向里填数据,消费者不断从中取数据
  • 两者不冲突的前提:
    • 缓冲区有若干个,且是固定大小,生产者和消费者各有若干个
    • 生产者向缓冲区中填数据前需要判断缓冲区是否满,满了则等待,直到有空间
    • 消费者从缓冲区中取数据前需要判断缓冲区是否为空,空了则等待,直到缓冲区有数据
    • 在某一时刻,缓冲区中只允许有一个操作者进行读或者写操作

通过信号量,则可以将以上提出的不冲突的前提一一解决。当信号量的value值为0时,则对当前信号量进行p操作(-1)时当前进程会挂起,直到信号量的value值为1。

当前生产者消费者模型并不友好,即生产者和消费者都需要受灾人口缓冲区前等待,判断是否能够写入或者取出,极大得浪费了系统空间。后期的优化应该为,当生产者写满之后向消费者发送一个可以来取信息的信号之后就离开,去做自己的事情。而消费者取完之后发送一个信号告诉生产者取完了,可以继续写入,消费者即可回去做自己的事情。

后期的优化待了解了进程信号处理方式之后合入当前生产者消费者模型之中

编程实例

  • 2个生产者每隔1秒向缓冲区中写入一次数据
  • 3个消费者每3秒,3秒,5秒从缓冲区中读出数据
  • 2个生产者分别对写信号量做P操作,对读信号量做V操作
  • 3个消费者分别对写信号量做V操作,对读信号量做P操作

    在这里插入图片描述

代码如下:

productor.c生产者

#include
#include 
#include 
#include 
#include #include 
#include 
#include 
#include //向获取到达共享内存的地址中写入值为data 的数据
//使用index表示当前数组下标同时表示当前缓冲区有多少个元素
//随着数据的不断写入,下标不断增加
void mem_write(int *addr, int data) { int index;index = addr[0];index ++;addr[index] = data;addr[0] = index;
}union semnum { int val;struct semid_ds *buf;unsigned short int *array;struct seminfo *__buf;
};
int sem_id;//初始化信号量的编号以及信号量的value值
void sem_init(int semid, int nsignum, int sem_value) { union semnum sem_union;sem_union.val = sem_value;if (semctl(semid,nsignum,SETVAL,sem_union) == -1) { printf("semctl failed
");_exit(-1);}
}//信号量-1操作,即编号为nsignum的信号量的value进行-1操作
void sem_p(int semid, int nsignum) { struct sembuf sops;sops.sem_num = nsignum;sops.sem_op = -1;sops.sem_flg = SEM_UNDO;if (semop(sem_id, &sops,1) == -1) { printf("semop P failed 
");_exit(-1);}}//信号量+1 操作,即编号为nsignum的信号量的value进行+1操作
void sem_v(int sem_id, int nsignum) { struct sembuf sops;sops.sem_num = nsignum;sops.sem_op = 1;sops.sem_flg = SEM_UNDO;if (semop(sem_id, &sops,1) == -1) { printf("semop V failed 
");_exit(-1);}
}//打印信号量,通过semctl获取信号量的数据结构,暂时没有用到
void sem_print(int sem_id, int nsignum) { int sem_value;sem_value = semctl(sem_id, nsignum, GETVAL);printf("sem[%d] = %d 
",nsignum, sem_value);
}int main() { //生成共享内存和信号量的keyint shm_id;key_t shm_key = ftok("./",111);key_t sem_key = ftok("./",112);//shmget获取共享内存的IPC标识,并进行内存的映射shmat得到共享内存的地址shm_id = shmget(shm_key, 1028, IPC_CREAT|0666);char *shm_addr = shmat(shm_id,NULL,0);//memset (shm_adr,0,sizeof(shm_addr));memset (shm_addr,0,128);//通过key生成信号量的IPC标识sem_id = semget(sem_key, 2,IPC_CREAT | 0666);if (sem_id == -1) { printf("semget failed
");_exit(-1);} else { sem_init(sem_id,0,0); //read sem//写信号量初始化为5,表示可以写5个缓冲区,即我们上图中描述的支持写入5个bufsem_init(sem_id,1,5); //write sem}int ret;if( (ret = fork()) == -1 ) { printf("fork failed
");_exit(-1);}//创建子进程,每隔一秒,写入数据else if (ret == 0) { int child_data = 1;while (1) { sleep(1);//写入之前将写信号量进行p操作(-1),表示写入一个缓冲区sem_p(sem_id,1);printf("child data  %d
",child_data);mem_write((int *)shm_addr,child_data);child_data += 2;//写之后将读信号量进行v操作(+1),表示支持读一个缓冲区sem_v(sem_id,0);}}//父进程每隔一秒写入数据else { int parent_data = 2;while(1) { sleep(1);sem_p(sem_id ,1);printf("parent_data %d
",parent_data);mem_write((int *)shm_addr, parent_data);parent_data = parent_data + 2;sem_v(sem_id,0);}}
}

consumer.c消费者

#include
#include 
#include 
#include 
#include #include 
#include 
#include 
#include //这里读时和mem_write相反,此时从共享内存中读出一个数据
//同时将index--;表示读出了一个数据,已经支持向内存写入
int mem_read(int *addr) { int index, data;index = addr[0];data = addr[index];index --;addr[0] = index;return data;
}union semnum { int val;struct semid_ds *buf;unsigned short int *array;struct seminfo *__buf;
};
int sem_id;void sem_init(int semid, int nsignum, int sem_value) { union semnum sem_union;sem_union.val = sem_value;if (semctl(semid,nsignum,SETVAL,sem_union) == -1) { printf("semctl failed
");_exit(-1);}
}void sem_p(int semid, int nsignum) { struct sembuf sops;sops.sem_num = nsignum;sops.sem_op = -1;sops.sem_flg = SEM_UNDO;if (semop(sem_id, &sops,1) == -1) { //printf("semop P failed 
");//_exit(-1);}}void sem_v(int sem_id, int nsignum) { struct sembuf sops;sops.sem_num = nsignum;sops.sem_op = 1;sops.sem_flg = SEM_UNDO;if (semop(sem_id, &sops,1) == -1) { //printf("semop v failed 
");// _exit(-1);}
}void sem_print(int sem_id, int nsignum) { int sem_value;sem_value = semctl(sem_id, nsignum, GETVAL);printf("sem[%d] = %d 
",nsignum, sem_value);
}int main() { //共享内存和信号量都支持key形式的对象生成int shm_id,sem_id;key_t shm_key = ftok("./",111);key_t sem_key = ftok("./",112);//获取共享内存的IPC标识,并进行内存的映射得到共享内存的地址shm_id = shmget(shm_key, 1028, IPC_CREAT|0666);char *shm_addr = shmat(shm_id,NULL,0);memset (shm_addr,0,128);//通过key生成信号量的IPC标识sem_id = semget(sem_key, 2,IPC_CREAT | 0666);if (sem_id == -1) { printf("semget failed
");_exit(-1);} else { sem_init(sem_id,0,0); //read semsem_init(sem_id,1,5); //write sem}//创建两个子进程读,每隔三秒读一个缓冲区//读之前将读信号量p(-1)操作,表示读一个信号量//读之后将写信号量v(+1)操作,表示支持写一个信号量for (int i = 0;i < 2; ++i) { int ret;if ((ret = fork()) == -1) { printf("fork failed
");_exit(-1);} else if (ret == 0) { while (1) { sleep(3);sem_p (sem_id, 0);printf("pid %d data :%d
",getpid(), mem_read((int *)shm_addr));sem_v (sem_id,1);}}}//父进程同样的方式也进行读while(1) { sleep(5);sem_p(sem_id , 0);printf("pid %d data :%d
",getpid(), mem_read((int *)shm_addr));sem_v(sem_id, 1);}return 0;
}

运行结果如下:

在这里插入图片描述

system V 信号量的通信特点

  • 信号量是通过标识而不是常用的文件描述符来引用的
  • 使用键而不是文件名来表示信号量
  • 创建,初始化,操作信号量需要单独的系统调用,分别为semget,semctl,semop;关于system V 信号量的详细使用可以参考文章linux进程间通信:system V 信号量
  • 信号量的操作同样会有阻塞现象。当信号量的value值为0时,则对当前信号量进行p操作(-1)时当前进程会挂起,直到信号量的value值为1。

更多相关:

  • 文章目录概念描述编程接口注意事项编程案例信号量基本接口使用案例信号量父子进程间通信信号量实现 两进程之间通信 概念描述 英文:semaphore 简称SEM,主要用来进行进程间同步本质:内核维护的一个正整数,可对其进行各种+/-操作分类:systemV 信号量、POSIX 有名信号量、POSIX 无名信号量用途:用来标示系统...

  • 关于信号量和共享内存的相关描述已经在前几篇提到过; 信号量:即内核维护的一个正整数,可以使用内核提供的p/v接口进行该正整数的+/-操作,它主要用来表示系统中可用资源的个数,协调各个进程有序访问资源,防止发生冲突共享内存:操作系统从运行中的进程拥有的3G用户空间中提供一段用户态可以直接访问的内存块,并且该内存区域可以被其他进程共享...

  •         Apache POI是一个开源的利用Java读写Excel,WORD等微软OLE2组件文档的项目。        我的需求是对Excel的数据进行导入或将数据以Excel的形式导出。先上简单的测试代码:package com.xing.studyTest.poi;import java.io.FileInputSt...

  • 要取得[a,b)的随机整数,使用(rand() % (b-a))+ a; 要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a; 要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1; 通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。 要取得a到b之间的...

  • 利用本征图像分解(Intrinsic Image Decomposition)算法,将图像分解为shading(illumination) image 和 reflectance(albedo) image,计算图像的reflectance image。 Reflectance Image 是指在变化的光照条件下能够维持不变的图像部分...

  • 题目:面试题39. 数组中出现次数超过一半的数字 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] 输出: 2 限制: 1 <= 数组长度 <= 50000 解题: cl...

  • 题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。 参考以下这颗二叉搜索树:      5     /    2   6   /  1   3示例 1: 输入: [1,6,3,2,5] 输出...

  • 空闲态:这时候手机只能使用一路信号,应该是最强的那一路。手机在空闲态时不断地搜索各个导频的强度,如果搜到比当前使用的导频更强的,那么它就自发的进行切换。这个切换的过程是手机自发的过程,不需要基站的参与。业务态:手机最多可以使用3路导频信号,并且将这3路导频信号进行最大比合并,也就是说将3个信号合并成更强的信号。如果手机测量到有更强的导...

  • 文章目录概念描述通信原理编程接口使用流程编程案例 概念描述 英文:semaphore 简称SEM,主要用来进行进程间同步本质:内核维护的一个正整数,可对其进行各种+/-操作分类:systemV 信号量、POSIX 有名信号量、POSIX 无名信号量用途:用来标示系统中可用资源的的个数,协调各个进程有序的访问资源,防止发生冲突...

  • 信号量概念理解 信号量本质上 是一个计数器,用来统计临界资源申请资源的个数。其中的二元信号量的 值是0或者是1,即是要么是有,要么是无。信号量本身也是临界资源,所以一定要保证其原子性。信号量的工作原理:两个进程共享一个信号量sv,一个进程访问的sv的时候,进行的是P操作即是减1操作,开始的时候信号量是1,它得到信号量进入临界资源。当...

  • osi七层协议 互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层 每层运行常见的物理设备 我们将应用层,表示层,会话层并作应用层,从tcp/ip五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议 就理解了整个互联网通信的原理。 首先,用户感知到的只是最上面一层应用层,自上而下每层都依赖于下一层,所...