fork和exec之间运行的线程阻塞其他线程读取

时间:2015-05-26 16:53:06

标签: linux multithreading

在研究通过使用vfork()而不是fork()来提高Recoll性能的可能性时,我遇到了fork()问题,我无法解释。

Recoll反复执行外部命令来翻译文件,以便示例程序执行的操作:它启动重复执行" ls"并回读输出。

以下问题不是真正的"一,在某种意义上,实际的程序不会做什么触发问题。我只是偶然发现它,同时看看fork()/ vfork()和exec()之间是否停止了什么线程。

当我有一个线程在fork()和exec()之间忙于循环时,另一个线程永远不会完成数据读取:最后一个read(),它应该指示eof,永远被阻塞或直到另一个线程& #39; s循环结束(此时一切都恢复正常,你可以通过用完成的循环替换无限循环来看到)。当read()被阻止时," ls"命令已退出(ps显示< defunct>,一个僵尸)。

这个问题有一个随机方面,但示例程序"成功"大多数时候。我测试了Linux内核3.2.0(Debian),3.13.0(Ubuntu)和3.19(Ubuntu)。适用于虚拟机,但至少需要2个触发器,我无法使用一个处理器。

以下是示例程序,我无法看到我做错了什么。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <iostream>

using namespace std;

struct thread_arg {
    int tnum;
    int loopcount;
    const char *cmd;
};

void* task(void *rarg)
{
    struct thread_arg *arg = (struct thread_arg *)rarg;
    const char *cmd = arg->cmd;

    for (int i = 0; i < arg->loopcount; i++) {
        pid_t pid;
        int pipefd[2];

        if (pipe(pipefd)) {
            perror("pipe");
            exit(1);
        }
        pid = fork();
        if (pid) {
            cerr << "Thread " << arg->tnum << " parent " << endl;
            if (pid < 0) {
                perror("fork");
                exit(1);
            }
        } else {
            // Child code. Either exec ls or loop (thread 1)
            if (arg->tnum == 1) {
                cerr << "Thread " << arg->tnum << " looping" <<endl;
                for (;;);
                //for (int cc = 0; cc < 1000 * 1000 * 1000; cc++);
            } else {
                cerr << "Thread " << arg->tnum << " child" <<endl;
            }

            close(pipefd[0]);
            if (pipefd[1] != 1) {
                dup2(pipefd[1], 1);
                close(pipefd[1]);
            }
            cerr << "Thread " << arg->tnum << " child calling exec" <<
                endl;
            execlp(cmd, cmd, NULL);
            perror("execlp");
            _exit(255);
        }

        // Parent closes write side of pipe
        close(pipefd[1]);
        int ntot = 0, nread;
        char buf[1000];
        while ((nread = read(pipefd[0], buf, 1000)) > 0) {
            ntot += nread;
            cerr << "Thread " << arg->tnum << " nread " << nread << endl;
        }
        cerr << "Total " <<  ntot << endl;

        close(pipefd[0]);
        int status;
        cerr << "Thread " << arg->tnum << " waiting for process " << pid
             << endl;
        if (waitpid(pid, &status, 0) != -1) {
            if (status) {
                cerr << "Child exited with status " << status << endl;
            }
        } else {
            perror("waitpid");
        }
    }

    return 0;
}

int main(int, char **)
{
    int loopcount = 5;
    const char *cmd =  "ls";

    cerr << "cmd [" << cmd << "]" << " loopcount " << loopcount << endl;

    const int nthreads = 2;
    pthread_t threads[nthreads];

    for (int i = 0; i < nthreads; i++) {
        struct thread_arg *arg = new struct thread_arg;
        arg->tnum = i;
        arg->loopcount = loopcount;
        arg->cmd = cmd;
        int err;
        if ((err = pthread_create(&threads[i], 0, task, arg))) {
            cerr << "pthread_create failed, err " << err << endl;
            exit(1);
        }
    }

    void *status;
    for (int i = 0; i < nthreads; i++) {
        pthread_join(threads[i], &status);
        if (status) {
            cerr << "pthread_join: " << status << endl;
            exit(1);
        }
    }
}

1 个答案:

答案 0 :(得分:2)

发生的事情是你的管道被两个子进程而不是一个进程继承。

您想要做的是:

  1. 创建2端管道
  2. fork(),child继承管道的两端
  3. child关闭读取结束,parent关闭写入结束
  4. ...以便孩子最终只有一个管道的一端,dup2()到stdout。

    但是你的线程彼此竞争,所以会发生什么:

    1. 线程1创建具有2端的管道
    2. 线程0创建具有2个端点的管道
    3. 主题1 fork() s。子进程继承了4个文件描述符,而不是2!
    4. 线程1的子进程关闭了线程1打开的管道的读取端,但它也保留了对线程0管道的读取结束和写入结束的引用。
    5. 稍后,线程0会永远等待,因为它正在读取它正在读取的管道上的EOF,因为该管道的写入端仍然由线程1的孩子保持打开。

      您需要定义一个在pipe()之前开始的关键部分,包含fork(),并在父项close()之后结束,并且只从一个主题的一个主题处输入该关键部分使用互斥锁的时间。