3

マルチスレッドの Python プロセスには、多数の非デーモンスレッドがあります。これは、メイン スレッドが終了/停止した後でもメイン プロセスを維持するスレッドを意味します。

私の非デーモン スレッドは、メイン スレッド内の特定のオブジェクトへの弱参照を保持していますが、メイン スレッドが終了すると (制御がファイルの下部に落ちます)、これらのオブジェクトはガベージ コレクションされていないように見えます。火。

メインスレッドがガベージコレクションされると期待するのは間違っていますか? スレッドローカルの割り当てが解除される(つまり、ガベージコレクションが行われる)と予想していました...

私は何を逃したのですか?


サポート資料

pprint.pprint( threading.enumerate() )メイン スレッドが停止し、他のスレッドが続行していることを示す出力。

[<_MainThread(MainThread, stopped 139664516818688)>,
 <LDQServer(testLogIOWorkerThread, started 139664479889152)>,
 <_Timer(Thread-18, started 139663928870656)>,
 <LDQServer(debugLogIOWorkerThread, started 139664437925632)>,
 <_Timer(Thread-17, started 139664463103744)>,
 <_Timer(Thread-19, started 139663937263360)>,
 <LDQServer(testLogIOWorkerThread, started 139664471496448)>,
 <LDQServer(debugLogIOWorkerThread, started 139664446318336)>]

そして、誰かが常にユースケースについて尋ねるので...

私のネットワーク サービスは、リアルタイムの締め切りに間に合わないことがあります (最悪の場合、システム全体の障害が発生します)。これは、(重要な) DEBUG データのロギングが、ファイルシステムに癇癪を起こすたびにブロックされるためであることが判明しました。そこで、I/O のブロッキングをワーカー スレッドに任せるために、多くの確立された特殊なログ ライブラリを改造しようとしています。

悲しいことに、確立された使用パターンは、重複する並列トランザクションをログに記録する短命のロギング チャネルと、明示的に閉じられることのない長寿命のモジュール スコープ チャネルの混合です。

そこで、ワーカー スレッドへのメソッド呼び出しを延期するデコレータを作成しました。ワーカー スレッドは非デーモンであり、インタープリターが終了する前にすべての (遅い) ブロッキング I/O が完了し、クライアント側 (メソッド呼び出しがエンキューされる場所) への弱い参照を保持します。クライアント側でガベージ コレクションが行われると、弱い参照のコールバックが起動し、ワーカー スレッドはキューに追加される作業がなくなることを認識し、次の都合のよいときに終了します。

これは、ロギング チャネルがメイン スレッドにある場合という 1 つの重要なユース ケースを除いて、すべて正常に機能するようです。メイン スレッドが停止または終了すると、ロギング チャネルはファイナライズされないため、(デーモンではない) ワーカー スレッドはプロセス全体を存続させます。

4

1 に答える 1

3

joinすべての非デーモン スレッドを呼び出さずにメイン スレッドを終了したり、そうしない場合に何が起こるかを仮定したりするのは、悪い考えです。


非常に珍しいことをしなければ、CPython (少なくとも2.0 - 3.3join ) は、すべての非デーモン スレッドを のペアとして自動的に呼び出すことでカバーします_MainThread._exitfunc。これは実際には文書化されていないため、それに依存するべきではありませんが、実際に起こっていることです。

メインスレッドは実際にはまったく終了していません。任意の非デーモンスレッド_MainThread._exitfuncへの試行中にブロックされています。joinそのオブジェクトは、atexitハンドラが呼び出されるまでファイナライズされません。これは、すべての非デーモン スレッドへの参加が完了するまで行われません。


一方、これを回避すると (たとえば、thread/_threadを直接使用するか、メイン スレッドをそのオブジェクトから切り離すか、通常のThreadインスタンスに強制することによって)、どうなりますか? 定義されていません。モジュールはそれthreadingをまったく参照しませんが、CPython 2.0-3.3、およびおそらく他の適切な実装では、決定するのはthread/_threadモジュールになります。そして、ドキュメントが言うように:

メイン スレッドが終了したときに、他のスレッドが存続するかどうかはシステムによって定義されます。ネイティブ スレッド実装を使用する SGI IRIX では、それらは存続します。他のほとんどのシステムでは、try ... finally 句を実行したり、オブジェクト デストラクタを実行したりせずに、それらを強制終了します。

したがって、join非デーモン スレッドをすべて回避することができた場合は、それらをデーモン スレッドのようにハード キルし、終了するまで実行し続けることの両方を処理できるコードを作成する必要があります。

少なくとも POSIX システム上の CPython 2.7 および 3.3 で実行し続ける場合、メイン スレッドの OS レベルのスレッド ハンドルと、それを表すさまざまな高レベルの Python オブジェクトは引き続き保持され、GC によってクリーンアップされない可能性があります。 .


その上、すべてが解放されたとしても、GC が何も削除しないとは限りません。コードが決定論的 GC に依存している場合、CPython で回避できるケースはたくさんあります (ただし、PyPy、Jython、IronPython などではコードが壊れます) が、終了時にはそれらの 1 つではありません。CPython は、終了時にオブジェクトをリークし、OS にそれらをソートさせることができます。(これが、決して閉じない書き込み可能なファイルが最後のいくつかの書き込みを失う可能性がある理由です。__del__メソッドは呼び出されないため、メソッドに指示する人は誰もいません。flush少なくとも POSIX では、基になるファイルFILE*も自動的にフラッシュしません。)

closeメインスレッドが終了したときに何かをクリーンアップしたい場合は、に依存するのではなく、ある種の関数を使用する__del__必要がありwith、コードのメインブロックの周りのブロック、atexit関数、または他のメカニズム。


最後に一つだけ:

スレッドローカルの割り当てが解除される(つまり、ガベージコレクションが行われる)と予想していました...

実際にどこかにスレッドローカルがありますか? それとも、1 つのスレッドでのみアクセスされるローカルおよび/またはグローバルを意味しますか?

于 2013-06-04T23:45:27.560 に答える