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

向管道写入时进程阻塞:写入量远小于缓冲区仍卡住

管道写入阻塞问题排查与解决

我在向管道写入数据与子进程通信时遇到阻塞。一开始以为是管道缓冲区满了,得先读才能继续写,查了系统管道最大缓冲区是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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 19:40:33