【Linux】进程间通信(三)——共享内存和消息队列
目录
一、共享内存
1、概念:
2、共享内存的使用
创建共享内存:shmget
建立映射:shmat;s可以访问内存空间
断开映射:shmdt;s不可以访问内存空间
删除共享内存:shmctl
3、共享内存操作
操作步骤:
具体实现:
结果:
4、优缺点:
二、消息队列
1、概念
2、消息队列的使用
创建消息队列:msgget
添加消息队列:msgsnd
获取消息:msgrcv
删除消息队列:msgctl
注意事项
3、消息队列的操作
具体实现
结果
4、优缺点
消息队列的优点
消息队列的缺点
一、共享内存
1、概念:
共享内存是一种进程间通信(IPC)机制,允许多个进程访问同一块物理内存区域,从而实现高效数据共享。由于无需数据拷贝,它是速度最快的IPC方式之一,但需要同步机制(如信号量、互斥锁)避免竞态条件。头文件为——<sys/sem.h>
2、共享内存的使用
创建共享内存:shmget
使用shmget()
函数,通过唯一键值(key_t
)创建或获取共享内存标识符。
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//创建共享内存
建立映射:shmat;s可以访问内存空间
通过shmat()
将共享内存映射到进程的虚拟地址空间。
char* s=(char*)shmat(shmid,NULL,0);//建立映射,s可以访问内存空间
断开映射:shmdt;s不可以访问内存空间
使用shmdt()
解除映射,但内存段仍存在直至显式删除。
shmdt(s);//断开映射,s不可以访问内存空间
删除共享内存:shmctl
shmctl()
用于删除(IPC_RMID
)或查询状态。
shmctl(shmid,IPC_RMID,NULL);//删除共享内存
3、共享内存操作
操作步骤:
- 引入信号量
- p操作
- v操作
- 销毁
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/sem.h>enum INDEX{SEM1 = 0,SEM2};union semun
{int val;
};void sem_init();//初始化
void sem_p(int index);//p
void sem_v(int index);//v
void sem_destroy();
具体实现:
a.c文件
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/shm.h>
#include"sem.h"int main(){int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//创建共享内存if(-1==shmid){exit(1);}char* s=(char*)shmat(shmid,NULL,0);//建立映射,s可以访问内存空间if(s==(char*)-1){exit(1);}sem_init();while(1){printf("input\n");char buff[128]={0};fgets(buff,128,stdin);//从键盘获取数据sem_p(SEM1);strcpy(s,buff);//写入内存中sem_v(SEM2);if( strncmp(buff,"end",3) == 0){//比较字符串的前三个字符endbreak;}}//strcpy(s,"hello");//向共享内存写入helloshmdt(s);//断开映射,s不可以访问内存空间return 0;
}
b.c文件
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/shm.h>
#include"sem.h"int main(){int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);if(-1==shmid){exit(1);}char* s=(char*)shmat(shmid,NULL,0);//建立映射,s可以访问内存空间if(s==(char*)-1){exit(1);}sem_init();while(1){sem_p(SEM2);if(strncmp(s,"end",3)==0){break;}printf("s=%s\n",s);sem_v(SEM1);sleep(1);}shmdt(s);//断开映射,s不可以访问内存空间shmctl(shmid,IPC_RMID,NULL);//删除共享内存sem_destroy();//销毁exit (0);}
sem.c文件
#include "sem.h"static int semid = -1;
#define SEM_NUM 2void sem_init()
{semid = semget((key_t)1234,SEM_NUM,IPC_CREAT|IPC_EXCL|0600);//尝试全新创建if( -1 == semid )//新建失败,说明已存在{semid = semget((key_t)1234,SEM_NUM,0600);if( -1 == semid ){printf("semget err\n");}}else{int arr[SEM_NUM] = {1,0};for(int i = 0; i < SEM_NUM; i++){union semun a;a.val = arr[i];if( semctl(semid,i,SETVAL,a) == -1){printf("semctl setval err\n");}}}
}
void sem_p(int index){struct sembuf buf;buf.sem_num = index;buf.sem_op = -1;//pbuf.sem_flg = SEM_UNDO;//进程异常结束,系统会自动归还信号量if( semop(semid,&buf,1) == -1){printf("p err\n");}
}
void sem_v(int index)
{struct sembuf buf;buf.sem_num = index;buf.sem_op = 1;//vbuf.sem_flg = SEM_UNDO;if( semop(semid,&buf,1) == -1){printf("v err\n");}
}
void sem_destroy()
{if( semctl(semid,0,IPC_RMID) == -1){printf("sem destroy err\n");}
}
结果:
4、优缺点:
优点
- 高性能:直接内存访问,无数据拷贝。
- 适合大规模数据交换。
缺点
- 需手动同步,编程复杂度高。
- 系统崩溃可能导致数据不一致。
二、消息队列
1、概念
消息队列是一种异步通信机制,用于解耦生产者和消费者。生产者将消息发送到队列,消费者从队列中获取并处理消息,双方无需直接交互。这种模式适用于分布式系统、流量削峰、异步任务等场景。头文件为——<sys/msg.h>
2、消息队列的使用
创建消息队列:msgget
在Linux系统中,消息队列通常通过msgget
系统调用创建。该函数需要指定一个键值(key)和一组标志(flags)。键值可以是IPC_PRIVATE
或通过ftok
生成的唯一键值。标志通常包括权限位(如0666
)和IPC_CREAT
。
int msgid=msgget((key_t)1234,IPC_CREAT|0600);
添加消息队列:msgsnd
消息通过msgsnd
函数发送到队列。消息需要包含一个长整型的消息类型和实际数据。结构体需以long mtype
开头,后面跟随数据内容。
msgsnd(msgid,(void*)&dt,128,0);
获取消息:msgrcv
消息通过msgrcv
函数从队列中接收。接收时需要指定消息类型(msgtyp
),若为0则接收队列中第一条消息。
//msgid消息队列id,dt接收数据存放的地点,128数据部分大小,0不区分类型,0标志位if(msgrcv(msgid,(void*)&dt,128,1,0)==-1){//接受消息类型为0,代表不区分消息类型exit(1);}
删除消息队列:msgctl
使用msgctl
函数并指定IPC_RMID
命令可以删除消息队列。
msgctl(msgid,IPC_RMID,NULL);
注意事项
- 消息队列是持久的,即使进程结束,队列仍会保留直到显式删除。
- 消息大小和队列容量受系统限制,可通过
/proc/sys/kernel/msgmax
和msgmnb
查看。 - 权限设置需合理,避免未授权访问。
3、消息队列的操作
具体实现
main.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/msg.h>struct mess{long type;char buff[128];
};
int main(){int msgid=msgget((key_t)1234,IPC_CREAT|0600);if(msgid==-1){exit(1);}struct mess dt;dt.type=1;strcpy(dt.buff,"hello");msgsnd(msgid,(void*)&dt,128,0);
}
tset.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/msg.h>struct mess{long type;char buff[128];
};
int main(){int msgid=msgget((key_t)1234,IPC_CREAT|0600);if(msgid==-1){exit(1);}struct mess dt;//msgid消息队列id,dt接收数据存放的地点,128数据部分大小,0不区分类型,0标志位if(msgrcv(msgid,(void*)&dt,128,1,0)==-1){//接受消息类型为0,代表不区分消息类型exit(1);}printf("read=%s\n",dt.buff);//msgctl(msgid,IPC_RMID,NULL);
}
结果
4、优缺点
消息队列的优点
异步处理
消息队列允许发送者和接收者异步工作,发送者将消息放入队列后无需等待接收者处理,提高系统响应速度和解耦性。
削峰填谷
在高并发场景下,消息队列能缓冲瞬时流量高峰,避免系统过载。后续由消费者逐步处理积压消息,平滑资源使用。
解耦系统组件
生产者和消费者通过队列通信,无需直接耦合。系统组件可独立扩展或修改,降低维护复杂度。
可靠性保障
多数消息队列提供持久化、重试和死信队列机制,确保消息不丢失。即使消费者暂时不可用,数据仍可恢复。
顺序性保证
部分队列(如Kafka分区)支持消息顺序处理,适用于需要严格时序的业务场景。
跨语言/平台通信
基于标准化协议(如AMQP),不同技术栈的系统可通过消息队列交互,无需依赖特定接口格式。
消息队列的缺点
系统复杂度增加
引入消息队列需额外维护中间件,部署监控、容灾等设施,增加架构复杂性和运维成本。
消息延迟
异步处理可能导致消息消费滞后,不适用于实时性要求极高的场景(如毫秒级响应)。
一致性问题
若消息消费失败或重复消费,可能引发数据不一致,需额外设计幂等性处理或事务补偿机制。
性能瓶颈
队列本身可能成为性能瓶颈,尤其在消息堆积时。需合理配置分区、副本等参数以优化吞吐量。
资源消耗
消息队列服务通常占用较高内存和存储资源,尤其在消息持久化场景下,硬件成本可能显著增加。
调试难度
分布式环境下,消息链路追踪和问题排查较复杂,需依赖日志、链路追踪等工具辅助。