在read(fds[0]...)
中分支特定流程时,以下代码有时会在spawn()
spawn()
上阻止。
#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;
}
请注意;在这里创建管道有点无用。它只是为了证明僵局。 read(fds[0], ...)
中的spawn()
永远不会阻止。调用read
后,管道的所有写入端都已关闭,这会导致read
立即返回。父进程中管道的写端显式关闭,子进程中的write-end由于文件描述符上设置FD_CLOEXEC
标志而隐式关闭,这将立即关闭文件描述符当execvp
成功时(在这种情况下它总是如此)。
这里的问题是我确实偶尔会看到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()
表现出相同的行为。答案 0 :(得分:1)
原因是,在之后你在这里创建了管道
// Thread A
int fds[2];
m.lock();
pipe(fds);
另一个线程可能只是vfork()
和exec
// Thread B
if (vfork() == 0) {
execvp("NullXServer", const_cast<char**>(args));
_exit(0);
}
在设置文件描述符标志之前:
// Thread A again...
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();
因此生成的B子进程将继承由线程A创建的文件描述符。
应该有助于扩展互斥锁以包含vfork()/execvp()
以迁移此效果。
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);
if (vfork() == 0) {
execvp("NullXServer", const_cast<char**>(args));
_exit(0);
}
m.unlock();