在研究通过使用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);
}
}
}
答案 0 :(得分:2)
发生的事情是你的管道被两个子进程而不是一个进程继承。
您想要做的是:
fork()
,child继承管道的两端 ...以便孩子最终只有一个管道的一端,dup2()
到stdout。
但是你的线程彼此竞争,所以会发生什么:
fork()
s。子进程继承了4个文件描述符,而不是2!稍后,线程0会永远等待,因为它正在读取它正在读取的管道上的EOF,因为该管道的写入端仍然由线程1的孩子保持打开。
您需要定义一个在pipe()
之前开始的关键部分,包含fork()
,并在父项close()
之后结束,并且只从一个主题的一个主题处输入该关键部分使用互斥锁的时间。