【发布时间】:2016-10-29 19:42:53
【问题描述】:
在 Linux 应用程序中,我通过 fork/execvp 生成多个程序,并将标准 IO 流重定向到 IPC 管道。我生成了一个子进程,将一些数据写入子标准输入管道,关闭标准输入,然后从标准输出管道读取子响应。这工作得很好,直到我同时执行了多个子进程,每个子进程使用独立的线程。
当我增加线程数时,我经常发现子进程在从标准输入读取时挂起——尽管read 应该立即以 EOF 退出,因为标准输入管道已被父进程关闭。
我已设法在以下测试程序中重现此行为。在我的系统(Fedora 23、Ubuntu 14.04;g++ 4.9、5、6 和 clang 3.7)上,程序通常会在退出三四个子进程后挂起。尚未退出的子进程挂在read()。杀死任何尚未退出的子进程会导致所有其他子进程神奇地从read() 唤醒,程序继续正常运行。
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <sys/fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#define HANDLE_ERR(CODE) \
{ \
if ((CODE) < 0) { \
perror("error"); \
quick_exit(1); \
} \
}
int main()
{
std::mutex stdout_mtx;
std::vector<std::thread> threads;
for (size_t i = 0; i < 8; i++) {
threads.emplace_back([&stdout_mtx] {
int pfd[2]; // Create the communication pipe
HANDLE_ERR(pipe(pfd));
pid_t pid; // Fork this process
HANDLE_ERR(pid = fork());
if (pid == 0) {
HANDLE_ERR(close(pfd[1])); // Child, close write end of pipe
for (;;) { // Read data from pfd[0] until EOF or other error
char buffer;
ssize_t bytes;
HANDLE_ERR(bytes = read(pfd[0], &buffer, 1));
if (bytes < 1) {
break;
}
// Allow time for thread switching
std::this_thread::sleep_for(std::chrono::milliseconds(
100)); // This sleep is crucial for the bug to occur
}
quick_exit(0); // Exit, do not call C++ destructors
}
else {
{ // Some debug info
std::lock_guard<std::mutex> lock(stdout_mtx);
std::cout << "Created child " << pid << std::endl;
}
// Close the read end of the pipe
HANDLE_ERR(close(pfd[0]));
// Send some data to the child process
HANDLE_ERR(write(pfd[1], "abcdef\n", 7));
// Close the write end of the pipe, wait for the process to exit
int status;
HANDLE_ERR(close(pfd[1]));
HANDLE_ERR(waitpid(pid, &status, 0));
{ // Some debug info
std::lock_guard<std::mutex> lock(stdout_mtx);
std::cout << "Child " << pid << " exited with status "
<< status << std::endl;
}
}
});
}
// Wait for all threads to complete
for (auto &thread : threads) {
thread.join();
}
return 0;
}
编译使用
g++ test.cpp -o test -lpthread --std=c++11
请注意,我非常清楚将 fork 和线程混合使用可能很危险,但请记住,在原始代码中,我在分叉后立即调用 execvp,而我没有任何子子进程和主程序之间的共享状态,专门为 IPC 创建的管道除外。我的原始代码(没有线程部分)可以在here找到。
在我看来,这几乎像是 Linux 内核中的一个错误,因为只要我杀死任何挂起的子进程,程序就会继续正确运行。
【问题讨论】:
-
您
forked 关闭的其他进程可能也打开了该管道。只有在关闭对另一端的最后一个引用时,管道才会关闭。 -
我检查了管道 fd,它们在所有进程/线程中都是唯一的。在上面的代码中,一个管道究竟是如何在多个进程之间共享的?
-
我想我现在明白了:如果代码在 pipe() 和 fork() 之间中断,多个进程可能拥有同一个管道...
标签: c++ c linux multithreading pipe