命名信号量在进程间读写同步中未按预期工作的问题
问题描述
- 预期行为:启动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操作共享内存,这些都会导致程序异常或不符合预期。
修复后的运行流程
- Writer启动后创建共享内存和初始值为0的信号量,写入数据后等待8秒,再通过
sem_post将信号量设为1。 - Reader启动后打开已存在的共享内存和信号量,调用
sem_wait阻塞,直到Writer释放信号量后才读取输出数据。 - 程序退出时,Writer会删除共享内存和信号量,避免残留影响后续运行。
内容的提问来源于stack exchange,提问作者melon
相关产品推荐
相关产品推荐

