【发布时间】:2017-10-11 09:53:12
【问题描述】:
当在spawn() 中派生特定进程时,以下代码有时会阻塞spawn() 中的read(fds[0]...)。
#include <fcntl.h>
#include <unistd.h>
#include <atomic>
#include <mutex>
#include <thread>
#include <vector>
void spawn()
{
static std::mutex m;
static std::atomic<int> displayNumber{30000};
std::string display{":" + std::to_string(displayNumber++)};
const char* const args[] = {"NullXServer", display.c_str(), nullptr};
int fds[2];
m.lock();
pipe(fds);
int oldFlags = fcntl(fds[0], F_GETFD);
fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC);
oldFlags = fcntl(fds[1], F_GETFD);
fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC);
m.unlock();
if (vfork() == 0) {
execvp("NullXServer", const_cast<char**>(args));
_exit(0);
}
close(fds[1]);
int i;
read(fds[0], &i, sizeof(int));
close(fds[0]);
}
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < 100; ++i) {
threads.emplace_back(spawn);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
注意;在这里创建管道有点没用。这样做只是为了演示死锁。 spawn() 中的 read(fds[0], ...) 不应该阻塞。一旦调用read,管道的所有写端都已关闭,这将导致read 立即返回。由于文件描述符上设置了FD_CLOEXEC标志,父进程中管道的写端是显式关闭的,子进程中的写端是隐式关闭的,它会在@时立即关闭文件描述符987654330@ 成功(在这种情况下总是如此)。
这里的问题是我确实看到 read() 偶尔阻塞。
全部替换:
m.lock();
pipe(fds);
int oldFlags = fcntl(fds[0], F_GETFD);
fcntl(fds[0], F_SETFD, oldFlags | FD_CLOEXEC);
oldFlags = fcntl(fds[1], F_GETFD);
fcntl(fds[1], F_SETFD, oldFlags | FD_CLOEXEC);
m.unlock();
作者:
pipe2(fds, O_CLOEXEC);
修复了阻塞读取,即使这两段代码至少应该导致 FD_CLOEXEC 被自动设置为管道文件描述符。
很遗憾,我在我们部署的所有平台上都没有pipe2。
谁能解释为什么read 会使用pipe 方法阻塞上述代码?
更多观察:
- 扩展互斥锁以覆盖
vfork()块可解决阻塞读取问题。 - 没有一个系统调用失败。
- 使用
fork()代替vfork()表现出相同的行为。 - 产生的进程很重要。在这种情况下,会在特定显示器上生成一个“空”X 服务器进程。例如,在这里分叉 'ls' 不会阻塞,或者发生阻塞的可能性要低得多,我不确定。
- 可在 Linux 2.6.18 到 4.12.8 上重现,所以我认为这不是某种 Linux 内核问题。
- 可使用 GCC 4.8.2 和 GCC 7.2.0 重现。
【问题讨论】:
标签: linux multithreading fork