共享内存与同步机制专题
共享内存是最快的 IPC,但需要配合同步原语。
一、共享内存用法
1. POSIX mmap 临时文件(推荐)
int fd = open("/tmp/myshm", O_CREAT|O_RDWR, 0666);
ftruncate(fd, SIZE);
void *p = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 关闭 fd 也不影响映射
2. POSIX 共享内存对象(更便携)
int fd = shm_open("/myregion", O_CREAT|O_RDWR, 0666);
ftruncate(fd, SIZE);
void *p = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
3. POSIX 共享内存 /dev/shm
- Linux 上 shm_open 在
/dev/shm下 - 重启自动清理
4. System V 共享内存(旧)
int shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT | 0666);
void *p = shmat(shmid, NULL, 0);
5. memfd_create(Linux 特有)
int fd = memfd_create("mybuf", 0);
ftruncate(fd, SIZE);
void *p = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
二、同步原语
1. POSIX 信号量(跨进程)
sem_t *sem = sem_open("/my_sem", O_CREAT, 0664, 1);
sem_wait(sem);
// 临界区
sem_post(sem);
sem_close(sem);
sem_unlink("/my_sem");
2. System V 信号量
- 见
07-ipc.md
3. futex(Linux 内核)
- 用户态 + 内核混合实现
- pthread_mutex 底层就是 futex
- 自定义锁可基于 futex 编写
#include <linux/futex.h>
#include <sys/syscall.h>
int futex(int *uaddr, int op, int val,
const struct timespec *timeout, int *uaddr2, int val3);
futex(&lock, FUTEX_WAIT, 0, NULL, NULL, 0); // 等待 lock==0
futex(&lock, FUTEX_WAKE, 1, NULL, NULL, 0); // 唤醒 1 个
三、无锁队列(Lock-free Queue)
1. 经典单生产者单消费者(SPSC)环形队列
struct spsc {
char *buf;
size_t cap;
atomic_size_t head; // 写
atomic_size_t tail; // 读
};
// 关键:写入完成后才发布 head;读取完成后才发布 tail
2. Michael-Scott 多生产者多消费者队列
- 基于 CAS
- ABA 问题需要小心
四、内存屏障
// GCC 内建
__sync_synchronize(); // 全屏障
__atomic_thread_fence(__ATOMIC_ACQUIRE); // acquire
__atomic_thread_fence(__ATOMIC_RELEASE); // release
规则:
- Acquire:紧随其后的读不会被重排到前
- Release:之前的写不会被重排到后
- Sequential Consistency:最强
五、性能对比
| 方式 | 延迟 | 吞吐 |
|---|---|---|
| pipe(4KB) | ~1us | 中 |
| Unix Domain Socket | ~1us | 中 |
| TCP localhost | ~10us | 中 |
| 共享内存 + 信号量 | < 100ns | 高 |
| 共享内存无锁 | < 50ns | 极高 |
六、典型模式
1. 共享内存 + 信号量(推荐入门)
struct shared_data {
pthread_mutex_t m;
int counter;
};
int shm_fd = shm_open("/my", O_CREAT|O_RDWR, 0666);
ftruncate(shm_fd, sizeof(struct shared_data));
void *p = mmap(NULL, sizeof(struct shared_data),
PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0);
// p->m 必须是 PTHREAD_PROCESS_SHARED(详见下)
关键:要让 pthread mutex 跨进程,必须:
pthread_mutexattr_t a;
pthread_mutexattr_init(&a);
pthread_mutexattr_setpshared(&a, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&((struct shared_data*)p)->m, &a);
2. eventfd + 共享内存
eventfd是个 64-bit 计数器- 用于事件通知(替代管道 / 信号)
- 可加入 epoll
七、调试
strace -e futex看 futex 争抢perf lock contention -a看锁竞争helgrind/DRD(Valgrind) 找数据竞争- ThreadSanitizer (
-fsanitize=thread)