多线程中fork的坑

 多线程中fork的坑


问题所在

在写oj的时候,由于使用了线程池,并且在获取用户程序运行结果的时候使用的是管道进行子进程的标准输出的获取,
最后带来了一个问题,就是发现本来线程池有5个任务,最后调试信息的打印确没有5个,而且个数不确定。

初步怀疑

是不是线程池出现了死锁的情况。

添加线程池任务

    void addTask(clTask* newTask)
    {
        pthread_mutex_lock(&pthreadMutex);
        allTask.push(newTask);
        pthread_mutex_unlock(&pthreadMutex);
        pthread_cond_broadcast(&pthreadCond);
    }

线程主函数

void* clPthread::pthreadMain(void *arg)
{
    clPthread& self = *(clPthread*)arg;
    while(1)
    {
        pthread_mutex_lock(&(self.pthreadMutex));
        while(0 == self.allTask.size())
        {
            //阻塞在条件变量上并且解锁互斥锁
            pthread_cond_wait(&(self.pthreadCond),&(self.pthreadMutex));
            //条件变量满足后,加锁
        }
        clTask* dealTask = self.allTask.front();
        self.allTask.pop();
        self.dealTask.push(dealTask);

        pthread_mutex_unlock(&(self.pthreadMutex));
        dealTask->run();
    }
}

这是很标准的的生产者消费者模型了,应该不会出现死锁的情况。但还是怀疑,所以后来我在每次线程池运行的任务的地方打印调试信息,
发现任务是都执行了的,并且是5次,并没有出现死锁的情况。那么问题可能出现在下面的代码中了。

找到问题

再往下面走就是编译和运行的函数了,oj的编译和运行我是采用fork进程然后重定向子进程的标准输出来获取用户程序的编译错误信息和
运行情况的,获取子进程状态和回收子进程使用的是wait函数,至于为什么使用wait函数,因为我的想法是让父进程阻塞等待子进程的编译和运行情况。
问题就出现在这里

由于我是在线程中fork的子进程,子进程结束的时候会触发wait函数
但是此时的wait函数的触发其实不一定是之前的线程fork的子进程触发的,可能是其他线程fork的子进程,因为所有线程fork的子进程的ppid都一样
等于是所有的子进程结束都会使得wait苏醒,但是这个wait确不是应该苏醒的那一个。

void clCompiler::compile()
{
    int fd[2];
    int ret = pipe(fd);
    int res;
    pid_t pid;
    chdir(workDir.c_str());
    solution->prepareCode();
    pid = fork();
    if(pid > 0){
        char buf[1024];
        close(fd[1]);
        //问题所在的地方
        wait(&res);
        read(fd[0],buf,sizeof(buf));
        close(fd[0]);
        if(res == 0){
            //编译成功
            this->status = COMPLIE_OK;

        } else{
            //编译失败
            this->status = COMPLIE_ERROR;
        }
        this->complieError = buf;
    }else {
        //查看ppid都是同一个
        printf("ppid:%d\n",getppid());
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        dup2(fd[1], STDERR_FILENO);

        this->doCompile();
        close(fd[1]);
        exit(0);
    }
}

如何解决

既然找到问题了,那么解决也不是很难了,只需要使用函数waitpid指定要回收的进程id就行。

虽然修改一个bug很简单,但是发现问题所在才是需要耐心和能力的。

阅读更多

没有更多推荐了,返回首页