spawn()
で特定のプロセスをフォークするときに、次のコードがread(fds[0]...)
をspawn()
にブロックすることがあります。pipe2の代わりにパイプを使用するとfork後にパイプ上のブロックを読み取る
#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
に戻ります。親プロセスのパイプの書き込み終了は明示的に閉じられ、ファイル記述子に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();
:コードの両方の部分が、少なくともFD_CLOEXEC
がパイプファイルディスクリプタのためにアトミックに設定さをもたらすはずであるにもかかわらず
pipe2(fds, O_CLOEXEC);
修正ブロック読み出しの全てを交換
。
残念ながら、私が配備するすべてのプラットフォームでpipe2
を利用することはできません。
が上記のコードでpipe
のアプローチを使用してブロックされる理由を誰かが明らかにすることはできますか?
いくつかのより多くの観測:vfork()
ブロックをカバーするためにmutexロックを拡張
- 読み取りブロッキングを解決します。
- システムコールが1つも失敗しません。
vfork()
の代わりにfork()
を使用すると同じ動作を示します。- 産卵プロセスは重要です。この場合、 'null' Xサーバープロセスが特定のディスプレイに生成されます。例えば、ここでの「ls」のフォークはブロックされないか、ブロックが発生する可能性はかなり低くなります。わかりません。
- Linux 2.6.18から4.12.8まで再現可能ですので、これはLinuxカーネルの問題ではありません。
- GCC 4.8.2とGCC 7.2.0の両方を使用して再現可能です。
ありがとうございます。私は何らかの理由で持っていた印象のようなファイル記述子フラグを設定している間に実際には1つのスレッドしか実行されないことを保証するものではありません。 –
@TonvandenHeuvel恥ずかしい必要はありません、並行コードはあまりにもしばしば正しく考えるためのピタです;) – Ctx