Linux下C语言阻塞管道生产者消费者进程的结束信号问题
解决方案:利用管道的EOF特性实现结束信号
首先,你当前的问题根源有两个:
- 用
null字符作为结束标志不适合多生产者/消费者场景——多个生产者会写入多个null,消费者读到第一个就停止,导致剩余数据未被读取;而消费者多于生产者时,部分消费者永远等不到null。 - 文件描述符传递错误:你通过
execl传递&n(int指针),但execl需要字符串参数,这会导致生产者/消费者拿到的文件描述符完全错误,这可能是程序异常的隐藏原因。
核心思路:利用管道的EOF机制
Unix管道的特性是:当所有写端文件描述符都被关闭时,read()会返回0,表示到达文件末尾(EOF)。我们可以利用这个特性作为消费者的结束信号:
- 所有生产者写完数据后,关闭自己的管道写端。
- 当最后一个生产者关闭写端后,管道的所有写端都关闭,此时消费者调用
read()会返回0,即可停止读取并退出。
具体代码修改步骤
1. 修正主程序的文件描述符传递逻辑
原来的execl传递&n是错误的,需要将文件描述符转为字符串传递:
// 主程序中创建生产者的部分修改 case 0: if((n = dup(pipeT[1])) == -1) exit(5); close(pipeT[0]); char prod_fd[10]; sprintf(prod_fd, "%d", n); // 将文件描述符转为字符串 if(execl("./producer", "producer", prod_fd, NULL) == -1) exit(4); break; // 创建消费者的部分修改 case 0: if((n = dup(pipeT[0])) == -1) exit(5); close(pipeT[1]); char cons_fd[10]; sprintf(cons_fd, "%d", n); if(execl("./consumer", "consumer", cons_fd, NULL) == -1) exit(4); break;
2. 修改生产者代码:移除null字符,写完后关闭写端
int main(int argc, char *argv[]) { srand(getpid()); int n = atoi(argv[1]); // 修正:将字符串转为整数文件描述符 char sign; char nameOfFile[20]; sprintf(nameOfFile, "P/p_%d", getpid()); FILE* f = fopen(nameOfFile, "w"); if(f == NULL) exit(1); int numberOfSigns = rand()%100; for(int i = 0; i < numberOfSigns; i++) { sign = rand()%94 + 32; if (write(n, &sign, sizeof(char)) == -1) exit(EXIT_FAILURE); fprintf(f, "%c", sign); } // 移除写null字符的代码 fclose(f); close(n); // 写完后关闭自己的管道写端 return 0; }
3. 修改消费者代码:通过read返回值判断结束
int main(int argc, char *argv[]) { int n = atoi(argv[1]); // 修正:将字符串转为整数文件描述符 char sign; char nameOfFile[20]; sprintf(nameOfFile, "K/k_%d", getpid()); FILE* f = fopen(nameOfFile, "w"); if(f == NULL) exit(1); // 当read返回>0时读取数据,返回0表示EOF,返回-1表示错误 ssize_t read_bytes; while((read_bytes = read(n, &sign, sizeof(char))) > 0) { fprintf(f, "%c", sign); } if(read_bytes == -1) { exit(EXIT_FAILURE); } fclose(f); close(n); // 关闭管道读端 return 0; }
为什么这个方案可行?
- 当所有生产者都写完数据并关闭写端后,管道的所有写端都被关闭,此时消费者的
read()会返回0,触发循环结束,不会提前终止。 - 无论生产者和消费者数量如何匹配,消费者都会读取到管道中的所有数据,直到EOF才停止,确保数据不丢失。
- 修正了文件描述符传递的错误,保证管道读写的正确性。
额外注意事项
- 确保
P/和K/目录存在,否则生产者/消费者创建文件会失败,可以在主程序开头添加mkdir("P", 0755); mkdir("K", 0755);(需要包含sys/stat.h和sys/types.h)。 - 主程序中关闭
pipeT[0]和pipeT[1]的时机是对的:创建完所有子进程后,主程序不再需要管道的两端,关闭它们不会影响子进程的dup后的fd。
内容的提问来源于stack exchange,提问作者Zar
相关产品推荐
相关产品推荐

