如何避免共享内存读取器的忙等待?寻求可行同步方案
多进程共享内存同步方案:唤醒所有读取端+无忙等待
核心解决思路
针对你遇到的问题,推荐使用POSIX健壮条件变量+广播唤醒的组合方案,同时通过引用计数和健壮锁处理读取端意外退出的一致性问题:
- 用
pthread_cond_broadcast替代sem_post,一次性唤醒所有阻塞的读取进程 - 使用
PTHREAD_MUTEX_ROBUST类型互斥锁,自动处理读取进程崩溃后的锁状态异常 - 维护读取进程引用计数,配合条件变量的循环等待逻辑,避免虚假唤醒和资源泄漏
共享内存同步结构定义
首先在共享内存中定义统一的同步控制块:
#include <pthread.h> #include <stdint.h> #define MAX_DATA_SIZE 1024 typedef struct { // 进程间共享的健壮互斥锁 pthread_mutex_t mutex; // 进程间共享的条件变量 pthread_cond_t cond; // 活跃读取进程计数 uint32_t reader_count; // 数据就绪标记(避免虚假唤醒) int data_ready; // 数据缓冲区 char data[MAX_DATA_SIZE]; // 数据有效长度 size_t data_len; } SharedMemSync;
写入端实现
负责更新共享内存数据,并广播唤醒所有读取端:
#include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #define SHM_NAME "/my_shared_mem" int main() { // 创建/打开共享内存 int shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666); ftruncate(shm_fd, sizeof(SharedMemSync)); SharedMemSync* sync = mmap(NULL, sizeof(SharedMemSync), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); // 初始化同步结构(仅首次启动执行) pthread_mutexattr_t mutex_attr; pthread_mutexattr_init(&mutex_attr); pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); pthread_mutexattr_setrobust(&mutex_attr, PTHREAD_MUTEX_ROBUST); pthread_mutex_init(&sync->mutex, &mutex_attr); pthread_condattr_t cond_attr; pthread_condattr_init(&cond_attr); pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED); pthread_cond_init(&sync->cond, &cond_attr); sync->reader_count = 0; sync->data_ready = 0; // 模拟持续写入数据 while (1) { pthread_mutex_lock(&sync->mutex); // 写入数据到共享缓冲区 const char* new_data = "Updated data from writer"; sync->data_len = strlen(new_data); memcpy(sync->data, new_data, sync->data_len); sync->data_ready = 1; // 广播唤醒所有等待的读取进程 pthread_cond_broadcast(&sync->cond); pthread_mutex_unlock(&sync->mutex); sleep(1); // 模拟每秒一次写入 } // 退出清理(实际场景需处理信号触发的优雅退出) pthread_mutex_destroy(&sync->mutex); pthread_cond_destroy(&sync->cond); munmap(sync, sizeof(SharedMemSync)); shm_unlink(SHM_NAME); return 0; }
读取端实现
处理锁的健壮性恢复,避免忙等待,同时维护活跃计数:
#include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> #define SHM_NAME "/my_shared_mem" int main() { // 打开共享内存 int shm_fd = shm_open(SHM_NAME, O_RDWR, 0666); SharedMemSync* sync = mmap(NULL, sizeof(SharedMemSync), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); // 注册为活跃读取进程 pthread_mutex_lock(&sync->mutex); sync->reader_count++; pthread_mutex_unlock(&sync->mutex); while (1) { int lock_ret; // 处理前一个持有锁的进程崩溃的情况 while ((lock_ret = pthread_mutex_lock(&sync->mutex)) == EOWNERDEAD) { // 尝试恢复锁的一致性(需确保共享结构状态合法) if (pthread_mutex_consistent(&sync->mutex) != 0) { perror("Failed to recover mutex"); goto exit_cleanup; } } if (lock_ret != 0) { perror("Mutex lock failed"); goto exit_cleanup; } // 循环等待数据就绪,处理虚假唤醒 while (!sync->data_ready) { pthread_cond_wait(&sync->cond, &sync->mutex); } // 读取并打印数据 printf("Reader got data: %.*s\n", (int)sync->data_len, sync->data); // 标记数据已读取(若需多读取端确认,可改为计数逻辑) sync->data_ready = 0; pthread_mutex_unlock(&sync->mutex); usleep(500000); // 模拟数据处理时间 } exit_cleanup: // 退出时更新活跃计数 pthread_mutex_lock(&sync->mutex); sync->reader_count--; pthread_mutex_unlock(&sync->mutex); munmap(sync, sizeof(SharedMemSync)); return 1; }
关键细节解释
- 健壮互斥锁:当持有锁的读取进程意外退出时,
pthread_mutex_lock会返回EOWNERDEAD,此时调用pthread_mutex_consistent可恢复锁的可用状态,避免死锁 - 条件变量广播:
pthread_cond_broadcast会唤醒所有阻塞在该条件变量上的读取进程,解决sem_post只能唤醒单个进程的问题 - 循环等待:用
while (!sync->data_ready)包裹pthread_cond_wait,有效处理操作系统触发的虚假唤醒 - 引用计数:跟踪活跃读取进程,可扩展写入端的退出逻辑(如等待所有读取端退出后再销毁共享内存)
内容的提问来源于stack exchange,提问作者nothingisme
相关产品推荐
相关产品推荐

