向管道写入时进程阻塞:写入量远小于缓冲区仍卡住
管道写入阻塞问题排查与解决
我在向管道写入数据与子进程通信时遇到阻塞。一开始以为是管道缓冲区满了,得先读才能继续写,查了系统管道最大缓冲区是1048576字节,但我只写160000字节,完全没到上限,搞不懂为啥阻塞。
以下是相关代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <fcntl.h> #include <math.h> #define SIZE 320000 #define FILENAME "Practical_Q_1numbers.txt" extern long fun(long a, long b); void write_numbers(int process_id,int* pipefds, long* numbers) { long process_numbers[SIZE / 8]; memcpy(process_numbers, &numbers[SIZE / 8 * process_id], SIZE / 8 * sizeof(long)); printf("This gets printed\n"); // Pipe Limit write(pipefds[1], process_numbers, SIZE / 8 * sizeof(long)); printf("This doesnt\n"); } // Correct long calculate(long* numbers, int left, int right) { if(left == right){ return numbers[left]; }else if(left + 1 == right) { return fun(numbers[left], numbers[right]); } int middle = (right + left) / 2; long l_ans = calculate(numbers, left, middle); long r_ans = calculate(numbers, middle + 1, right); return fun(l_ans, r_ans); } void calculateHelper(int id, int* pipefds) { long* ptr = (long*)malloc(SIZE / 8 * sizeof(long)); read(pipefds[0], ptr, SIZE / 8 * sizeof(long)); long res = calculate(ptr, 0, SIZE / 8 - 1); // write(pipefds[1], &res, sizeof(res)); return; } int main() { // Read the file as parent. FILE *myFile; myFile = fopen(FILENAME, "r"); long* ptr = (long*)malloc(SIZE * sizeof(long)); // Reads correctly for(int i = 0; i < SIZE; i++) { fscanf(myFile, "%ld", &ptr[i]); } int pipefds[8][2]; for(int i = 0; i < 8; i++) { pipe(pipefds[i]); } for(int i = 0; i < 1; i++) { write_numbers(i,pipefds[i], ptr); pid_t a = fork(); if(a == 0) { // Child process calculateHelper(i,pipefds[i]); exit(0); } } // Wait for your children to terminate while(wait(NULL) > 0); // long* finalContenders = (long*) malloc(8 * sizeof(long)); // for(int i = 0; i < 8; i++) { // read(pipefds[i][0], &finalContenders[i], sizeof(long)); // } // long ans = calculate(finalContenders, 0, 7); // printf("%ld\n",ans); }
注:fun函数用于计算两个数的GCD(最大公约数)。
问题根源
代码里的执行顺序完全搞反了:先调用write_numbers写入管道,再fork创建子进程。管道的读端在父进程中没有关闭,且子进程是在写入操作之后才创建的——这意味着执行写入时,根本没有任何进程在监听管道的读端。管道默认是阻塞模式,当写入的数据量超过内核允许的无阻塞写入阈值(远小于最大缓冲区),且没有读进程取走数据时,write就会一直阻塞,直到有进程读取管道内容。
解决方法
调整执行顺序,先创建子进程,再在父进程中执行写入操作,同时要正确管理管道的文件描述符,避免资源泄漏:
修改main函数中的循环逻辑:
for(int i = 0; i < 1; i++) { pid_t a = fork(); if(a == 0) { // 子进程:关闭不需要的写端 close(pipefds[i][1]); calculateHelper(i,pipefds[i]); exit(0); } else { // 父进程:关闭不需要的读端 close(pipefds[i][0]); write_numbers(i,pipefds[i], ptr); // 写完后关闭写端,让子进程的read知道数据已全部写入 close(pipefds[i][1]); } }
同时在calculateHelper中补充内存释放和文件描述符关闭:
void calculateHelper(int id, int* pipefds) { long* ptr = (long*)malloc(SIZE / 8 * sizeof(long)); read(pipefds[0], ptr, SIZE / 8 * sizeof(long)); long res = calculate(ptr, 0, SIZE / 8 - 1); // write(pipefds[1], &res, sizeof(res)); free(ptr); // 释放malloc分配的内存 close(pipefds[0]); // 关闭读端 return; }
补充说明
- 管道的最大缓冲区是1048576字节,但阻塞写入的触发条件不是缓冲区满,而是当当前缓冲区无法容纳写入数据,且没有读进程在读取时,就会触发阻塞。
- 必须严格管理管道的文件描述符:每个进程只保留自己需要的端(父进程留写端,子进程留读端),用完及时关闭,否则管道的引用计数不为0,子进程的
read会一直等待更多数据。
内容的提问来源于stack exchange,提问作者Arya J
相关产品推荐
相关产品推荐

