ツイスト:複数のスレッドとプロセスを一緒に使用する
-
15-11-2019 - |
質問
Twisted のドキュメントを読むと、次のようなテクニックを組み合わせても問題ないと考えるようになりました。 reactor.spawnProcess()
そして threads.deferToThread()
同じアプリケーションでは、リアクターがこれを内部でエレガントに処理することになります。実際に試してみると、アプリケーションがデッドロックしてしまうことがわかりました。複数のスレッドを単独で使用したり、子プロセスを単独で使用したりしても、すべて問題ありません。
リアクターのソースを調べてみると、 SelectReactor.spawnProcess()
メソッドは単に呼び出すだけです os.fork()
実行されている可能性のある複数のスレッドについては考慮されていません。これでデッドロックの説明がつきます。 os.fork()
複数の同時スレッドが実行され、同じファイル記述子で何を行うかは誰にも分からない 2 つのプロセスが存在します。
SOへの私の質問は、この問題を解決するための最良の戦略は何ですか?
私が考えているのはサブクラス化することです SelectReactor
, 、シングルトンであるため、呼び出します os.fork()
インスタンス化されたときにただちに 1 回だけ。子プロセスはバックグラウンドで実行され、親のサーバーとして機能します (パイプを介したオブジェクトのシリアル化を使用して相互に通信します)。親はアプリケーションを実行し続け、必要に応じてスレッドを使用できます。に電話をかける spawnProcess()
親のプロセスは子プロセスに委任され、子プロセスでは 1 つのスレッドのみが実行されることが保証されるため、 os.fork()
安全に。
これまでに誰かがこれをやったことがありますか?もっと早い方法はありますか?
解決 3
しばらくしてこの問題に戻ると、これを実行すると次のことがわかりました。
reactor.callFromThread(reactor.spawnProcess, *spawnargs)
これの代わりに:
reactor.spawnProcess(*spawnargs)
私の小さなテストケースでは問題は解決します。Twisted のドキュメント「Using Processes」に次の記述があり、これをきっかけにこれを試してみました。「Twisted のほとんどのコードはスレッドセーフではありません。たとえば、プロトコルからトランスポートにデータを書き込むことはスレッドセーフではありません。」
ジャン=ポールがこの問題を抱えていると言及した他の人たちも同様の間違いを犯しているのではないかと思います。リアクターおよびその他の API 呼び出しが正しいスレッド内で行われるようにするのはアプリケーションの責任です。そして明らかに、非常に狭い例外を除いて、「正しいスレッド」はほぼ常にメインのリアクター スレッドです。
他のヒント
この問題を解決するための最良の戦略は何でしょうか?
チケットを提出する (おそらくその後 登録する) できれば再現可能なテスト ケースを使用して問題を説明します (精度を最大限に高めるため)。次に、それを実装するための最良の方法 (複数の方法 - プラットフォームごとに異なるソリューションが必要になる可能性があります) は何かについて議論することができます。
子プロセスの取得に関するパフォーマンスの問題を解決するために、子プロセスをすぐに作成して、その後の子プロセスの作成を支援するというアイデアが以前に提起されました。このアプローチで 2 つの問題が解決されると、もう少し魅力的に見え始めます。このアプローチの潜在的な問題の 1 つは、 spawnProcess
子の PID を提供し、信号を子に送信できるようにするオブジェクトを同期的に返します。途中に中間プロセスがある場合、実装する前に PID をメイン プロセスに返す必要があるため、これを実装するには少し手間がかかります。 spawnProcess
戻り値。同様の挑戦が、 childFDs
子プロセスでファイル記述子を単に継承することはできなくなるためです。
別の解決策 (これは多少ハッキング的かもしれませんが、実装上の課題も少ないかもしれません) は、次のように呼び出すことかもしれません。 sys.setcheckinterval
電話をかける前に非常に大きな番号を付けてください os.fork
, を実行し、親プロセスのみで元のチェック間隔を復元します。これは、プロセス中のスレッドの切り替えを回避するには十分です。 os.execvpe
が発生し、余分なスレッドがすべて破棄されます。これは、特定のリソース (ミューテックスや条件など) を悪い状態のままにするため、完全に正しいわけではありませんが、これらを次のように使用します。 deferToThread
あまり一般的ではないので、あなたの場合には影響しないかもしれません。
ジャン=ポールが答えの中で与えたアドバイスは良いですが、これは すべき 動作します(そしてほとんどの場合動作します)。
まず、Twisted はホスト名解決にもスレッドを使用します。そして、クライアント接続も行う Twisted プロセスのサブプロセスを間違いなく使用しました。したがって、これは実際に機能する可能性があります。
2番、 fork()
子プロセス内に複数のスレッドを作成しません。 記載されている規格によると、 fork()
,
プロセスは単一のスレッドで作成されます。マルチスレッド プロセスが fork() を呼び出す場合、新しいプロセスには呼び出しスレッドのレプリカが含まれます。
さて、それがあるというわけではありません いいえ マルチスレッドに関する潜在的な問題 spawnProcess
;規格には次のようにも記載されています。
...エラーを回避するために、子プロセスは、いずれかの exec 関数が呼び出されるまで、非同期シグナルセーフ操作のみを実行できます。
そして、非同期シグナルセーフな操作のみが使用されることを保証するものは何もないと思います。
したがって、スレッドが複製されるサブプロセスではないため、正確な問題をより具体的にしてください。
Linux の fork() は、子プロセスにスレッドを 1 つだけ残します。
Twisted でスレッドを使用する場合、スレッドが呼び出すことができる唯一の Twisted API は callFromThread であることをご存知だと思います。他のすべての Twisted API は、メインのリアクター スレッドからのみ呼び出す必要があります。