You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免共享内存读取器的忙等待?寻求可行同步方案

多进程共享内存同步方案:唤醒所有读取端+无忙等待

核心解决思路

针对你遇到的问题,推荐使用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;
}

关键细节解释

  1. 健壮互斥锁:当持有锁的读取进程意外退出时,pthread_mutex_lock会返回EOWNERDEAD,此时调用pthread_mutex_consistent可恢复锁的可用状态,避免死锁
  2. 条件变量广播:pthread_cond_broadcast会唤醒所有阻塞在该条件变量上的读取进程,解决sem_post只能唤醒单个进程的问题
  3. 循环等待:用while (!sync->data_ready)包裹pthread_cond_wait,有效处理操作系统触发的虚假唤醒
  4. 引用计数:跟踪活跃读取进程,可扩展写入端的退出逻辑(如等待所有读取端退出后再销毁共享内存)

内容的提问来源于stack exchange,提问作者nothingisme

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 16:32:41