C 为什么关闭管道需要如此长的时间来终止子进程?

C 为什么关闭管道需要如此长的时间来终止子进程?,c,pthreads,pipe,fork,C,Pthreads,Pipe,Fork,我的程序在等待子进程(gzip)完成时遇到了问题,并且花了很长时间 在开始等待之前,它会将输入流关闭到gzip,因此这会触发它很快终止。我已经检查了系统,并且gzip没有消耗任何CPU或等待IO(写入磁盘) 非常奇怪的是它停止等待的时间 该程序允许我们在内部使用pthreads。它并行处理4个pthreads。每个线程处理许多工作单元,对于每个工作单元,它启动一个新的gzip进程(使用fork()和execve())来写入结果。当gzip没有终止时,线程挂起,但当其他线程关闭其实例时,它会突然终

我的程序在等待子进程(
gzip
)完成时遇到了问题,并且花了很长时间

在开始等待之前,它会将输入流关闭到
gzip
,因此这会触发它很快终止。我已经检查了系统,并且
gzip
没有消耗任何CPU或等待IO(写入磁盘)

非常奇怪的是它停止等待的时间


该程序允许我们在内部使用pthreads。它并行处理4个pthreads。每个线程处理许多工作单元,对于每个工作单元,它启动一个新的
gzip
进程(使用
fork()
execve()
)来写入结果。当
gzip
没有终止时,线程挂起,但当其他线程关闭其实例时,它会突然终止

为了清楚起见,我正在设置一个管道:
myprogram(pthread)-->gzip-->file.gz

我想这可以部分解释为CPU负载。但是当进程间隔几分钟启动时,由于这个锁定问题,整个系统最终只使用4个内核中的1个,这似乎不太可能

启动gzip的代码如下所示。调用
execPipeProcess
时,子进程直接写入文件,但从我的程序中读取。即:

execPipeProcess(&process, "gzip", -1, gzFileFd)
有什么建议吗

typedef struct {
    int processID;
    const char * command;
    int stdin;
    int stdout;
} ChildProcess;


void closeAndWait(ChildProcess * process) {
    if (process->stdin >= 0) {
                stdLog("Closing post process stdin");
                if (close(process->stdin)) {
                exitError(-1,errno, "Failed to close stdin for %s",  process->command);
                }
        }
    if (process->stdout >= 0) {
                stdLog("Closing post process stdin");
                if (close(process->stdout)) {
            exitError(-1,errno, "Failed to close stdout for %s", process->command);
                }
        }

    int status;
        stdLog("waiting on post process %d", process->processID);
    if (waitpid(process->processID, &status, 0) == -1) {
        exitError(-1, errno, "Could not wait for %s", process->command);
    }
        stdLog("post process finished");

    if (!WIFEXITED(status)) exitError(-1, 0, "Command did not exit properly %s", process->command);
    if (WEXITSTATUS(status)) exitError(-1, 0, "Command %s returned %d not 0", process->command, WEXITSTATUS(status));
    process->processID = 0;
}



void execPipeProcess(ChildProcess * process, const char* szCommand, int in, int out) {
    // Expand any args
    wordexp_t words;
    if (wordexp (szCommand, &words, 0)) exitError(-1, 0, "Could not expand command %s\n", szCommand);


    // Runs the command
    char nChar;
    int nResult;

    if (in < 0) {
        int aStdinPipe[2];
        if (pipe(aStdinPipe) < 0) {
            exitError(-1, errno, "allocating pipe for child input redirect failed");
        }
        process->stdin = aStdinPipe[PIPE_WRITE];
        in = aStdinPipe[PIPE_READ];
    }
    else {
        process->stdin = -1;
    }
    if (out < 0) {
        int aStdoutPipe[2];
        if (pipe(aStdoutPipe) < 0) {
            exitError(-1, errno, "allocating pipe for child input redirect failed");
        }
        process->stdout = aStdoutPipe[PIPE_READ];
        out = aStdoutPipe[PIPE_WRITE];
    }
    else {
        process->stdout = -1;
    }

    process->processID = fork();
    if (0 == process->processID) {
        // child continues here

        // these are for use by parent only
        if (process->stdin >= 0) close(process->stdin);
        if (process->stdout >= 0) close(process->stdout);

        // redirect stdin
        if (STDIN_FILENO != in) {
            if (dup2(in, STDIN_FILENO) == -1) {
              exitError(-1, errno, "redirecting stdin failed");
            }
            close(in);
        }

        // redirect stdout
        if (STDOUT_FILENO != out) {
            if (dup2(out, STDOUT_FILENO) == -1) {
              exitError(-1, errno, "redirecting stdout failed");
            }
            close(out);
        }

        // we're done with these; they've been duplicated to STDIN and STDOUT

        // run child process image
        // replace this with any exec* function find easier to use ("man exec")
        nResult = execvp(words.we_wordv[0], words.we_wordv);

        // if we get here at all, an error occurred, but we are in the child
        // process, so just exit
        exitError(-1, errno, "could not run %s", szCommand);
  } else if (process->processID > 0) {
        wordfree(&words);
        // parent continues here

        // close unused file descriptors, these are for child only
        close(in);
        close(out);
        process->command = szCommand;
    } else {
        exitError(-1,errno, "Failed to fork");
    }
}
typedef结构{
int进程ID;
const char*命令;
int-stdin;
int标准输出;
}儿童过程;
void closeAndWait(ChildProcess*进程){
如果(进程->标准输入>=0){
标准日志(“结束后处理标准”);
如果(关闭(流程->标准输入)){
exitError(-1,错误号,“无法关闭%s的标准输入代码”,进程->命令);
}
}
如果(进程->标准输出>=0){
标准日志(“结束后处理标准”);
如果(关闭(进程->标准输出)){
exitError(-1,错误号,“无法关闭%s的标准输出”,进程->命令);
}
}
智力状态;
stdLog(“正在等待后处理%d”,处理->处理ID);
if(waitpid(进程->进程ID和状态,0)=-1){
exitError(-1,errno,“无法等待%s”,进程->命令);
}
标准日志(“后处理完成”);
如果(!WIFEXITED(status))退出错误(-1,0,“命令未正确退出%s”,进程->命令);
如果(WEXITSTATUS(status))exitError(-1,0,“命令%s返回%d而不是0”,进程->命令,WEXITSTATUS(status));
进程->进程ID=0;
}
void execPipeProcess(ChildProcess*process,const char*szCommand,int-in,int-out){
//展开任何参数
单词词汇量;
如果(wordexp(szCommand,&words,0))exitror(-1,0,“无法展开命令%s\n”,szCommand);
//运行命令
查恩查尔;
结果;
if(in<0){
int aStdinPipe[2];
如果(管道(aStdinPipe)<0){
exitError(-1,errno,“为子输入重定向分配管道失败”);
}
进程->标准输入=aStdinPipe[PIPE_WRITE];
in=aStdinPipe[管道读取];
}
否则{
进程->标准输入=-1;
}
如果(输出<0){
int aStdoutPipe[2];
if(管道(aStdoutPipe)<0){
exitError(-1,errno,“为子输入重定向分配管道失败”);
}
process->stdout=aStdoutPipe[管道读取];
out=aStdoutPipe[管道写入];
}
否则{
进程->标准输出=-1;
}
进程->进程ID=fork();
如果(0==进程->进程ID){
//孩子继续在这里
//这些仅供家长使用
如果(进程->标准输入>=0)关闭(进程->标准输入);
如果(进程->标准输出>=0)关闭(进程->标准输出);
//重定向标准
如果(标准文件号!=in){
if(dup2(in,STDIN_FILENO)=-1){
exitError(-1,errno,“重定向stdin失败”);
}
关闭(in);
}
//重定向标准输出
如果(标准输出文件号!=输出){
如果(dup2(输出,标准输出文件号)=-1){
exitError(-1,errno,“重定向标准输出失败”);
}
收尾;
}
//我们已经完成了这些;它们已经复制到STDIN和STDOUT
//运行子进程映像
//将其替换为更易于使用的任何exec*函数(“man exec”)
nResult=execvp(words.we\u wordv[0],words.we\u wordv);
//如果我们到了这里,就发生了一个错误,但我们是在孩子身上
//进程,所以退出
exitError(-1,errno,“无法运行%s”,szCommand);
}else if(进程->进程ID>0){
wordfree(&words);
//家长继续在这里
//关闭未使用的文件描述符,这些描述符仅适用于儿童
关闭(in);
收尾;
进程->命令=szCommand;
}否则{
exitError(-1,errno,“未能分叉”);
}
}

子进程继承打开的文件描述符

每个后续的gzip子进程不仅继承用于与该特定实例通信的管道文件描述符,还继承连接到先前子进程实例的管道的文件描述符

这意味着,当主进程执行关闭时,stdin管道仍然处于打开状态,因为在几个子进程中,同一管道还有一些其他文件描述符。一旦这些管道终止,管道最终关闭

一个快速修复方法是通过设置close on exec标志来防止子进程继承用于主进程的管道文件描述符


由于涉及多个线程,因此应序列化子进程以防止子进程继承用于另一个子进程的管道FD。

子进程继承打开的文件描述符

每个后续的gzip子进程不仅继承用于与特定ins通信的管道文件描述符