基于C语言的共享内存生产者-消费者同步故障排查求助
我仔细看了你的代码,发现几个核心逻辑错误导致程序卡住,下面逐个拆解并给出修复方案:
1. Consumer进程根本没被创建
你的main.c里只执行了一次fork(),然后错误地用同一个pid变量尝试启动Consumer。第一个fork()后,父进程中的pid是Producer子进程的ID(非0),所以第二个switch(pid)的case 0永远不会触发——也就是说,Consumer进程从来没被启动过!
修复方式:在父进程里再调用一次fork()来创建Consumer,调整后的main.c核心代码如下:
// 创建Producer子进程 pid_t pid_prod = fork(); switch(pid_prod) { case -1: perror("fork() error in producer section"); exit(2); case 0: execl("./producer", "./producer", NULL); perror("execl() error in producer section"); exit(3); default: // 父进程继续创建Consumer子进程 pid_t pid_cons = fork(); switch(pid_cons) { case -1: perror("fork() error in consumer section"); exit(4); case 0: execl("./consumer", "./consumer", NULL); perror("execl() error in consumer section"); exit(5); default: break; } break; }
2. 信号量操作完全搞反了
生产者-消费者模型的信号量逻辑是:
- 空槽信号量(sem0,初始值SIZE):生产者必须先"获取"(P操作)它,确保有空闲槽位才能写数据;写完后"释放"(V操作)满槽信号量(sem1,初始值0),告诉消费者有数据可读。
- 满槽信号量(sem1):消费者必须先"获取"(P操作)它,确保有数据才能读;读完后"释放"(V操作)空槽信号量(sem0),把槽位还给生产者。
但你的代码完全颠倒了这个逻辑:
- Producer里先
semRelease(semid, 0)(给空槽信号量加1),然后写数据,接着semAcquire(semid, 1)(等待满槽信号量>0,而初始值是0,这里直接卡住!)。 - Consumer里先
semRelease(semid, 1)(给满槽信号量加1),然后读数据,接着semAcquire(semid, 0)(等待空槽信号量>0),逻辑完全错误。
修复方式:
- Producer循环内的信号量操作改为:
semAcquire(semid, 0); // 等待空槽可用 usleep(rand()%5555); buf->bufor[i%SIZE] = c; buf->size++; semRelease(semid, 1); // 通知消费者有新数据 printf(" %c", c); fflush(stdout); // 强制刷新输出,避免缓冲看不到实时内容
- Consumer循环内的信号量操作改为:
semAcquire(semid, 1); // 等待有数据可读 usleep(rand()%5555); c = buf->bufor[i%SIZE]; semRelease(semid, 0); // 释放空槽给生产者 fputc(c, fp); printf(" %c", c); fflush(stdout); i++; c_cnt++;
3. Producer/Consumer重复创建信号量
Main进程已经创建了信号量集合,Producer和Consumer应该获取已有的信号量,而不是再次调用semCreate。如果你的semCreate用了IPC_CREAT | IPC_EXCL标志,重复调用会失败返回-1,后续信号量操作出错导致卡住。
修复方式:
把Producer和Consumer里的semid = semCreate(key, 2);改为semid = semGet(key);(如果你的工具函数里没有semGet,可以直接用semget(key, 2, 0)来获取已有信号量)。
4. Producer代码的笔误
你Producer里有一行y memoryID = memoryAccess('A');,多了个y,应该改成int memoryID = memoryAccess('A');——这个笔误会导致编译错误,Producer根本无法运行。
5. Consumer循环终止条件不可靠
原来的!((semctl(semid, 0, GETVAL) == SIZE) && (c_cnt == buf->size))容易出现竞态条件,比如生产者刚写完数据,信号量值还没更新,消费者就提前退出了。
修复方式:给共享结构体加一个结束标志,让生产者写完后通知消费者停止:
// 修改共享结构体 typedef struct cyclicalBuf { int size; int is_done; // 生产者完成后设为1 char bufor[SIZE]; } cyclicalBuf;
然后Producer在循环结束后设置buf->is_done = 1;,并额外释放一次满槽信号量唤醒消费者:
// Producer循环结束后 buf->is_done = 1; semRelease(semid, 1); // 唤醒可能等待的消费者
Consumer的循环条件改为:
while(!(buf->is_done && (c_cnt == buf->size))) { semAcquire(semid, 1); // 额外判断,避免生产者结束后还继续等待 if(buf->is_done && c_cnt >= buf->size) { semRelease(semid, 1); break; } // ... 原有操作 }
额外小贴士
- 所有
printf后加fflush(stdout):终端输出默认是行缓冲,不刷新的话可能看不到实时的处理过程。 - 确保
input.txt存在:否则Producer会直接报错退出。 - 检查你的信号量封装函数:比如
semAcquire要正确调用semop执行减1操作,semRelease执行加1操作。
内容的提问来源于stack exchange,提问作者Taknie

