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

命名信号量在进程间读写同步中未按预期工作的问题

问题描述
  • 预期行为:启动Writer进程后,等待约2秒再启动Reader进程,Reader应阻塞至Writer完成数据写入并释放信号量后,才输出共享内存中的数据。
  • 实际结果:Reader启动后几乎立即输出了共享内存中的数据,未按预期阻塞等待。
相关代码

writer.c

// macOS编译:gcc -o w writer.c -lpthread
// Linux编译:gcc -o w writer.c -lrt -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "shmem.h"

int main(){
    int fd = shm_open(BackingFile, O_RDWR | O_CREAT, AccessPerms);  // 读写+创建
    if(fd < 0){
        report_and_exit("无法打开共享内存段");
    }
    ftruncate(fd, ByteSize);                       // 调整后备文件大小

    caddr_t memptr = mmap(
        NULL,                                      // 让系统选择共享内存段位置
        ByteSize,                                  // 字节数
        PROT_READ | PROT_WRITE,                    // 访问权限
        MAP_SHARED,                                // 共享内存对其他进程可见
        fd,                                        // 文件描述符
        0                                          // 偏移量:从起始位置开始
    );
    if((caddr_t) -1 == memptr){                    // 检查mmap是否成功
        report_and_exit("无法获取内存段...");
    }
    fprintf(stderr, "共享内存地址: %p [0..%d]\n", memptr, ByteSize - 1);
    fprintf(stderr, "后备文件: /dev/shm%s\n", BackingFile );

    sem_t* semptr = sem_open(                      // 创建信号量以锁定共享内存
        SemaphoreName,                             // 信号量名称
        O_CREAT,                                   // 操作:创建
        AccessPerms,                               // 保护权限
        0                                          // 初始值
    );
    if(semptr == (void*) -1){                      // 检查信号量创建是否成功
        report_and_exit("sem_open失败");
    }

    // fprintf(stderr, "信号量地址: %p\n", semptr);

    strcpy(memptr, MemContents);                   // 向共享内存写入数据

    int num = 8;                                   // 等待8秒,此时Reader不应访问
    while(num > 0){
        printf("%d\n", num);
        num--;
        sleep(1);
    }

    if(sem_post(semptr) < 0){                      // 写入完成后递增信号量,允许Reader访问
        report_and_exit("sem_post失败");
    }

    sleep(6);                                      // 等待Reader被调度执行

    // 清理操作
    munmap(memptr, ByteSize);                      // 解除内存映射
    close(fd);
    sem_close(semptr);
    sem_unlink(SemaphoreName);                     // 删除信号量,避免残留
    shm_unlink(BackingFile);                       // 解除与后备文件的关联
    return 0;
}

注:原代码中num--;m和sleep(1);d是笔误,已修正为num--;和sleep(1);,否则无法编译;新增sem_unlink用于清理信号量。

reader.c

// macOS编译:gcc -o r reader.c -lpthread
// Linux编译:gcc -o r reader.c -lrt -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>       
#include <fcntl.h>          
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "shmem.h"

int main(){
    int fd = shm_open(BackingFile, O_RDWR, AccessPerms);  // 读写,不创建
    if(fd < 0){
        report_and_exit("无法获取文件描述符...");
    }

    caddr_t memptr = mmap(                                // 映射到内存
        NULL,                                             // 让系统选择内存段位置
        ByteSize,                                         // 共享内存字节数
        PROT_READ | PROT_WRITE,                           // 访问权限
        MAP_SHARED,                                       // 共享内存对其他进程可见
        fd,                                               // 文件描述符
        0                                                 // 偏移量:从起始位置开始
    );
    if((caddr_t) -1 == memptr){                           // 检查映射是否成功
        report_and_exit("无法访问内存段...");
    } 

    sem_t* semptr = sem_open(                             // 使用信号量作为互斥锁
        SemaphoreName,                                    // 信号量名称
        0,                                                // 仅打开已存在的信号量,不创建
        AccessPerms,                                      // 保护权限(仅O_CREAT时有效)
        0                                                 // 初始值(仅O_CREAT时有效)
    );
    if(semptr == (void*) -1){                             // 检查信号量打开是否失败
        report_and_exit("sem_open失败");
    }

    if(!sem_wait(semptr)){                                // 等待信号量值非0,之后开始读取
        int i;
        for(i=0; i<strlen(MemContents); i++){
            write(STDOUT_FILENO, memptr+i, 1);            // 逐字节输出
        }
        sem_post(semptr);                                 // 递增信号量
    }

    // 清理操作
    munmap(memptr, ByteSize);                             // 解除内存映射
    close(fd);
    sem_close(semptr);
    shm_unlink(BackingFile);                              // 解除与后备文件的关联(若省略,文件会在程序退出后保留)
    return 0;
}

注:原代码中使用O_CREAT打开信号量是错误的,已改为0;原代码中unlink(BackingFile)错误,已改为shm_unlink(BackingFile)。

shmem.h

#define ByteSize 512
#define BackingFile "/shMemEx"
#define AccessPerms 0644
#define SemaphoreName "mysemaphore"
#define MemContents "This is the way the world ends...\n"

void report_and_exit(const char* msg){
    perror(msg);
    exit(-1);
}
问题原因与修复说明

核心问题1:命名信号量残留

命名信号量一旦创建,若不调用sem_unlink,即使所有进程关闭句柄,它仍会保留在系统中,且值不会重置。如果之前运行过Writer并执行了sem_post(将信号量设为1),后续再启动Writer时,sem_open(O_CREAT)会直接复用已存在的信号量,初始值参数被忽略,导致Reader的sem_wait直接通过。

核心问题2:Reader错误创建信号量

Reader的逻辑是等待Writer创建的信号量,不应自行创建。原代码中Reader用O_CREAT,若Writer未启动会创建新信号量导致永久阻塞,若信号量已存在则不符合设计逻辑。

核心问题3:代码笔误与调用错误

Writer的循环语句有语法错误,Reader误用普通文件的unlink操作共享内存,这些都会导致程序异常或不符合预期。

修复后的运行流程

  1. Writer启动后创建共享内存和初始值为0的信号量,写入数据后等待8秒,再通过sem_post将信号量设为1。
  2. Reader启动后打开已存在的共享内存和信号量,调用sem_wait阻塞,直到Writer释放信号量后才读取输出数据。
  3. 程序退出时,Writer会删除共享内存和信号量,避免残留影响后续运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:45:55