web-dev-qa-db-ja.com

スレッド内からフォークしても安全ですか?

説明させてください:私はすでにLinux上で外部バイナリをforkして実行し、終了するのを待つアプリケーションを開発しています。結果は、fork +プロセスに固有のshmファイルによって伝達されます。コード全体がクラス内にカプセル化されます。

今私は物事をスピードアップするためにプロセスをスレッド化することを検討しています。クラス関数の多くの異なるインスタンスを持つことで、(異なるパラメーターで)バイナリーをフォークして同時に実行し、独自の固有のshmファイルと結果を通信します。

このスレッドは安全ですか?スレッド内でフォークする場合、安全であることは別にして、注意する必要があるものはありますか?アドバイスやヘルプは大歓迎です!

42
Ælex

forkingは、スレッドがあっても安全です。フォークすると、スレッドはプロセスごとに独立しています。 (つまり、スレッドはforkと直交しています)。ただし、異なるプロセスのスレッドが同じ共有メモリを使用して通信する場合は、同期メカニズムを考案する必要があります。

0
Diego Sevilla

問題は、fork()が呼び出しスレッドのみをコピーし、子スレッドに保持されているミューテックスがforkされた子に永久にロックされることです。 pthreadソリューションはpthread_atfork()ハンドラーでした。アイデアは、3つのハンドラー(1つのプリフォーク、1つの親ハンドラー、および1つの子ハンドラー)を登録できることでした。 fork()が発生すると、preforkがforkの前に呼び出され、すべてのアプリケーションmutexを取得することが期待されます。親と子の両方が、親プロセスと子プロセスのすべてのミューテックスをそれぞれ解放する必要があります。

これで話は終わりではありません!ライブラリは_pthread_atfork_を呼び出して、ライブラリ固有のミューテックスのハンドラーを登録します。たとえば、Libcはこれを行います。これは良いことです。アプリケーションはサードパーティのライブラリが保持するmutexを認識できない可能性があるため、各ライブラリは_pthread_atfork_を呼び出して、fork()

問題は、無関係なライブラリに対して_pthread_atfork_ハンドラが呼び出される順序が定義されていないことです(ライブラリがプログラムによってロードされる順序に依存します)。つまり、これは技術的には、競合状態のためにpreforkハンドラー内でデッドロックが発生する可能性があることを意味します。

たとえば、次のシーケンスを考えてみます。

  1. スレッドT1呼び出しfork()
  2. libc preforkハンドラーがT1で呼び出されます(たとえば、T1はすべてのlibcロックを保持するようになりました)
  3. 次に、スレッドT2で、サードパーティライブラリAが独自のミューテックスAMを取得し、ミューテックスを必要とするlibc呼び出しを行います。 libcミューテックスはT1によって保持されているため、これはブロックします。
  4. スレッドT1は、ライブラリAのプリフォークハンドラーを実行します。これは、T2によって保持されているAMの取得を待機してブロックします。

あなたのデッドロックがあり、それはあなた自身のミューテックスやコードとは無関係です。

これは実際、私がかつて取り組んだプロジェクトで起こりました。当時私が見つけたアドバイスは、フォークかスレッドのどちらかを選択することでした。しかし、一部のアプリケーションでは、おそらく実用的ではありません。

58
Kevin

Forkとexecの間のコードに注意veryしている限り、マルチスレッドプログラムでforkしても安全です。そのスパンでは、再入可能(別名、非同期セーフ)システムコールのみを実行できます。理論的には、mallocまたはそこでの解放は許可されていませんが、実際にはデフォルトのLinuxアロケーターは安全であり、Linuxライブラリーはそれに依存するようになりました最終結果は、あなたでなければなりませんデフォルトのアロケータを使用します。

10
Igor Nazarenko

あなたがプログラムにLinuxのNPTL pthreads(7)サポートを使用することができる一方で、スレッドはUnixシステムでぎこちないフィットです。あなたのfork(2)質問。

fork(2)は最新のシステムでは非常に安価な操作であるため、次の場合にプロセスをfork(2)にした方がよい場合があります。実行する処理が多い。これは、前後に移動する予定のデータの量によって異なりますが、forkedプロセスのシェアーナッシングの哲学は、共有データのバグを減らすのに適していますが、移動するパイプを作成する必要があります プロセス間のデータ または共有メモリを使用(shmget(2)またはshm_open(3))。

しかし、スレッディングを使用することを選択した場合、fork(2)からの次のヒントを使用して、新しいプロセスをcanfork(2)できます(== --- ==)マンページ:

   *  The child process is created with a single thread — the
      one that called fork().  The entire virtual address space
      of the parent is replicated in the child, including the
      states of mutexes, condition variables, and other pthreads
      objects; the use of pthread_atfork(3) may be helpful for
      dealing with problems that this can cause.
6
sarnold

黎明期に戻って、スレッドを「軽量プロセス」と呼びました。スレッドはプロセスのように機能しますが、同一ではないためです。最大の違いは、スレッドは定義上、1つのプロセスの同じアドレス空間に存在することです。これには利点があります。スレッドからスレッドへの切り替えが高速であり、本質的にメモリを共有するため、スレッド間通信が高速で、スレッドの作成と破棄が高速です。

ここでの違いは、完全なアドレス空間である「ヘビーウェイトプロセス」です。 fork(2)によって新しいヘビーウェイトプロセスが作成されます。仮想メモリがUNIXの世界に入ってきたとき、それはvfork(2)と他のいくつかで増強されました。

A fork(2)は、すべてのレジスタを含むプロセスのアドレス空間全体をコピーし、そのプロセスをオペレーティングシステムスケジューラの制御下に置きます。次にスケジューラーが出てくると、命令カウンターは次の命令を受け取ります-フォークされた子プロセスは親のクローンです。 (別のプログラムを実行する場合、たとえば、シェルを作成しているために、フォークの後にexec(2)を呼び出すと、新しいアドレススペースが新しいプログラムでロードされ、クローンされたものです。)

基本的に、あなたの答えはその説明に埋もれています。 LWP スレッドを使用してプロセスをフォークすると、2つの独立したプロセスが多数のスレッドで同時に実行されます。

このトリックはさらに便利です。多くのプログラムでは、多くのスレッドを持つ親プロセスがあり、その一部は新しい子プロセスをフォークします。 (たとえば、HTTPサーバーがそれを行う場合があります:ポート80への各接続はスレッドによって処理され、CGIプログラムなどの子プロセスがフォークされます。exec(2)は親プロセスの代わりにCGIプログラムを実行するために呼び出されます。

3
Charlie Martin

フォークされた子プロセスでexec()または_exit()をすばやく呼び出すことができれば、実際には問題ありません。

代わりにposix_spawn()を使用することをお勧めします。

2
MarkR

UNIXの 'fork()'システムコールを使用している場合、技術的にはスレッドを使用していません。プロセスを使用しています。プロセスには独自のメモリ領域があるため、相互に干渉することはできません。

各プロセスが異なるファイルを使用している限り、問題はありません。

0
Kevin

スレッド内でのfork()の使用経験は本当に悪いです。ソフトウェアは一般的にかなりすぐに失敗します。

私は問題のいくつかの解決策を見つけましたが、あまり好きではないかもしれませんが、これらは一般にデバッグ不可エラーに近づかないようにするための最良の方法だと思います。

  1. 最初にフォーク

    最初に必要な外部プロセスの数がわかっていると仮定して、それらを事前に作成し、イベントを待機するようにそのまま置くことができます(つまり、ブロッキングパイプからの読み取り、セマフォでの待機など)。

    十分な数の子をフォークすると、スレッドを自由に使用し、パイプ、セマフォなどを介してフォークされたプロセスと通信できます。最初のスレッドを作成した時点から、フォークを呼び出すことはできなくなります。スレッドを作成する可能性のあるサードパーティのライブラリを使用している場合は、fork()呼び出しが発生した後にそれらを使用/初期化する必要があることに注意してください。

    その後、メインプロセスとfork() edプロセス内でスレッドの使用を開始できることに注意してください。

  2. あなたの状態を知る

    状況によっては、すべてのスレッドを停止してプロセスを開始してから、スレッドを再起動できる場合があります。これは、fork()を呼び出すときにスレッドを実行したくないという意味で、ポイント(1)に多少似ていますが、ソフトウェアで現在実行されているすべてのスレッドについて知る方法が必要です。 (サードパーティのライブラリでは常に可能とは限りません)。

    待機を使用した「スレッドの停止」は機能しないことに注意してください。このような待機にはミューテックスが必要であり、fork()を呼び出すときにロックを解除する必要があります。待機がいつミューテックスをアンロック/再ロックするかはわかりません。

  3. どちらかを選択してください

    もう1つの明白な可能性は、どちらか一方を選択し、どちらかに干渉するかどうかを気にしないことです。これは、ソフトウェアで可能な限り非常に簡単な方法です。

私のプログラミングでは、3つのソリューションすべてを使用しました。 _log4cplus_のスレッドバージョンと、ソフトウェアの一部でfork()を使用する必要があったため、ポイント(2)を使用しました。

他の人が述べたように、fork()を使用してexecve()を呼び出す場合は、2つの呼び出し間で使用する回数をできるだけ少なくするという考え方です。これは99.999%の確率で機能する可能性があります(多くの人がsystem()またはpopen()を使用してかなり成功し、これらも同様のことをしています)。

一方、私のようにfork()を実行し、execve()を呼び出さない場合は、スレッドが実行されている間は正しく機能しない可能性があります。

0
Alexis Wilke