多进程场景下如何在共享内存准备就绪前阻塞对其的访问?
多进程共享内存初始化竞态问题解决方案
你遇到的核心问题是「共享内存文件创建」和「初始化配置」两步操作不具备原子性,中间的时间窗口会导致其他进程访问未就绪的资源,你提到的flock方案是成本最低、可行性最高的解法,具体实现逻辑如下:
方案1:基于flock的排他锁控制(推荐)
flock是基于文件描述符的协同锁,刚好可以匹配共享内存文件的访问控制场景,不会出现死锁(进程异常退出后锁会自动释放),修改后的代码逻辑如下:
// 第一步:不管是创建还是打开,先获取共享内存fd int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); int is_creator = 0; if (fd < 0) { // 不是创建者,循环尝试打开,规避权限未设置导致的打开失败 while (1) { fd = shm_open(name, O_RDWR, S_IRUSR | S_IWUSR); if (fd >= 0) break; if (errno != EACCES) { // 非权限错误直接退出 perror("shm_open failed"); exit(1); } // 权限未就绪,短暂睡眠重试 usleep(1000); } } else { is_creator = 1; } // 第二步:所有进程拿到fd后先加排他锁,创建进程初始化完才会解锁,其他进程会阻塞在这里 if (flock(fd, LOCK_EX) < 0) { perror("flock failed"); close(fd); exit(1); } void *shm_addr = NULL; if (is_creator) { // 只有创建者需要做初始化操作 ftruncate(fd, sizeof(pthread_mutex_t)); // 先设置共享内存大小,原实现漏了这步 shm_addr = mmap(NULL, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // 初始化进程间共享的mutex pthread_mutexattr_t mutex_attr; pthread_mutexattr_init(&mutex_attr); pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); pthread_mutex_init((pthread_mutex_t *)shm_addr, &mutex_attr); pthread_mutexattr_destroy(&mutex_attr); // 初始化完成后改权限 fchmod(fd, S_IRUSR | S_IWUSR); } else { // 非创建者直接映射即可,此时已经加锁,共享内存一定是初始化完成的状态 shm_addr = mmap(NULL, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); } // 解锁,后续正常使用mutex即可 flock(fd, LOCK_UN); close(fd); // fd可以关闭,不影响mmap的映射和flock的状态 // 后续业务逻辑 pthread_mutex_lock((pthread_mutex_t *)shm_addr); // 临界区操作 pthread_mutex_unlock((pthread_mutex_t *)shm_addr);
常见问题说明
- 为什么非创建者要循环打开共享内存?
创建者刚调用shm_open的时候权限是只读的,未执行fchmod之前其他进程用O_RDWR打开会返回权限错误,所以需要加重试逻辑,避免启动时报错。 - 能不能直接让shm_open阻塞等待权限就绪?
标准POSIX的shm_open没有提供这个能力,它的语义是只要文件存在、权限匹配就立刻返回,没有等待就绪的参数,所以必须通过上层加锁或者轮询的方式实现。 - 有没有其他替代方案?
也可以在共享内存的头部预留一个就绪标记位,创建者初始化完成后将标记位改为1,其他进程打开后轮询等待标记位变为1即可,不过这种方案的效率比flock低,且需要处理轮询间隔的权衡问题。
内容的提问来源于stack exchange,提问作者J. Doe
相关产品推荐
相关产品推荐

