gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 12:14:26 26 4
gpt4 key购买 nike

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

Recoll 重复执行外部命令来翻译文件,这就是示例程序所做的:它启动重复执行“ls”并读回输出的线程。

以下问题不是“真实”问题,因为实际程序不会执行触发问题的操作。我只是在查看 fork()/vfork() 和 exec() 之间哪些线程停止或未停止时偶然发现了它。

当我有一个线程在 fork() 和 exec() 之间忙循环时,另一个线程永远不会完成数据读取:最后一个 read(),应该指示 eof,被永远阻塞,或者直到另一个线程的循环结束(此时一切恢复正常,您可以通过将无限循环替换为完成的循环来看到这一点)。当 read() 被阻塞时,“ls”命令已经退出(ps 显示 ,一个僵尸)。

这个问题有一个随机的方面,但示例程序在大多数情况下“成功”。我使用 Linux 内核 3.2.0 (Debian)、3.13.0 (Ubuntu) 和 3.19 (Ubuntu) 进行了测试。在 VM 上工作,但你至少需要 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. 创建两端的管道
  2. fork(),child继承管道两端
  3. child关闭读端,parent关闭写端

...这样 child 最终只得到一根管道的一端,管道被dup2()输出到标准输出。

但是你的线程相互竞争,所以会发生这样的事情:

  1. 线程 1 创建有两个末端的管道
  2. 线程 0 创建了两端的管道
  3. 线程 1 fork()。子进程继承了4个文件描述符,不是2个!
  4. 线程 1 的子进程关闭线程 1 打开的管道的读取端,但它也保留对线程 0 管道的读取端和写入端的引用。

稍后,线程 0 永远等待,因为它永远不会在它正在读取的管道上收到 EOF,因为该管道的写入端仍由线程 1 的子进程保持打开状态。

您需要定义一个临界区,它在 pipe() 之前开始,包含 fork(),在 close() 之后结束> 在父级中,并使用互斥锁一次仅从一个线程进入该临界区。

关于linux - 在 fork 和 exec 之间运行的线程会阻塞其他线程读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30472948/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com