共享内存 ( 最快 的进程间通信方式)
公享内存为多个进程之间共享和传递数据提供了一种有效的方式。
共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。
所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。
如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。
由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问
共享内存的四个核心函数
|----------|-----------|--------|
| 函数 | 作用 | 比喻 |
| shmget() | 创建/获取共享内存 | 租一块地 |
| shmat() | 连接到进程地址空间 | 拿到钥匙进门 |
| shmdt() | 断开连接 | 离开房子 |
| shmctl() | 控制(删除等) | 退租 |
int shmid = shmget(IPC_PRIVATE, 1024, 0666);
// IPC_PRIVATE: 创建新的共享内存
// 1024: 大小(字节)
// 0666: 读写权限
char *ptr = shmat(shmid, NULL, 0);
// shmid: 共享内存ID
// NULL: 让系统自动选择地址
// 0: 读写模式
// 返回值: 共享内存的首地址
shmdt(shared_memory);
// shared_memory: shmat()返回的地址
shmctl(shmid, IPC_RMID, NULL);
// IPC_RMID: 删除命令
例子:
cpp
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int shmid;
char *shared_memory;
// 1. 创建共享内存(大小1KB)
shmid = shmget(IPC_PRIVATE, 1024, 0666);
printf("1. 创建共享内存,ID = %d\n", shmid);
// 2. 连接到当前进程
shared_memory = shmat(shmid, NULL, 0);
printf("2. 连接到共享内存,地址 = %p\n", shared_memory);
pid_t pid = fork();
if (pid == 0) {
// 子进程:写入数据
printf("3. 子进程:写入数据\n");
strcpy(shared_memory, "Hello from Child!");
printf(" 子进程写了: %s\n", shared_memory);
} else {
// 父进程:读取数据
sleep(1); // 等子进程先写
printf("4. 父进程:读取数据\n");
printf(" 父进程读到: %s\n", shared_memory);
wait(NULL);
// 3. 断开连接
shmdt(shared_memory);
printf("5. 断开共享内存连接\n");
// 4. 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
printf("6. 删除共享内存\n");
}
return 0;
}
优点:
- ✅ 速度最快(直接内存访问)
- ✅ 大数据量友好(可分配很大内存)
- ✅ 使用简单(像普通内存一样操作)
缺点:
- ❌ 需要同步(多个进程同时写会冲突)
- ❌ 不会自动释放(要手动管理生命周期)
重要提醒:共享内存需要同步!
解决方案:
- 用信号量保护共享内存
- 用互斥锁同步访问
共享内存 + 信号量 = 完美组合
问题:多个进程同时写会冲突
解决方案:用信号量保护共享内存
cpp
#include <stdio.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
// 必须的联合体
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
// P/V操作
void P(int semid) { struct sembuf op = {0, -1, 0}; semop(semid, &op, 1); }
void V(int semid) { struct sembuf op = {0, 1, 0}; semop(semid, &op, 1); }
// 在共享内存中定义数据结构
struct shared_data {
int counter;
char message[100];
};
int main() {
int shmid, semid;
struct shared_data *shared;
union semun sem_arg;
// 1. 创建共享内存(包含我们的数据结构)
shmid = shmget(IPC_PRIVATE, sizeof(struct shared_data), 0666);
shared = (struct shared_data*)shmat(shmid, NULL, 0);
// 2. 创建信号量保护共享内存
semid = semget(IPC_PRIVATE, 1, 0666);
sem_arg.val = 1;
semctl(semid, 0, SETVAL, sem_arg);
// 初始化共享数据
shared->counter = 0;
strcpy(shared->message, "Initial");
printf("初始状态: counter=%d, message=%s\n", shared->counter, shared->message);
pid_t pid = fork();
if (pid == 0) {
// 子进程:修改共享数据
for (int i = 0; i < 3; i++) {
P(semid); // 获取锁
// 安全地修改共享数据
shared->counter++;
sprintf(shared->message, "Child modified %d", i);
printf("子进程: counter=%d, message=%s\n", shared->counter, shared->message);
V(semid); // 释放锁
sleep(1);
}
} else {
// 父进程:修改共享数据
for (int i = 0; i < 3; i++) {
P(semid); // 获取锁
// 安全地修改共享数据
shared->counter++;
sprintf(shared->message, "Parent modified %d", i);
printf("父进程: counter=%d, message=%s\n", shared->counter, shared->message);
V(semid); // 释放锁
sleep(1);
}
wait(NULL);
// 清理
shmdt(shared);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
}
return 0;
}
共享内存的持久化特性
共享内存会一直存在,直到:
- 被显式删除 (shmctl(IPC_RMID))
- 系统重启
- 使用 ipcrm 命令删除
共享内存的最佳实践
一定要做的:
- 总是用信号量同步
- 及时释放连接 (shmdt)
- 程序退出前清理 (shmctl(IPC_RMID))
要避免的:
- ❌ 直接操作共享内存不加锁
- ❌ 忘记断开连接
- ❌ 让共享内存一直存在(内存泄漏)