質問: Python2.7 初心者です。 サーバープログラムを書いています。TCPコネクションを管理している複数のスレッドオブジェクト(threading.thread)を一つのリストに格納しています。クライアントから切断されたときに、即座にソケットを破棄して、リストからも削除したいと思っています。 現在は、スレッドオブジェクトへの参照を持つリスト要素を直接delメソッドで削除しています。これでも動作は正常で、スレッド自体は終了しているように見えます。ですが、スレッド内のreturnを経由している気配がないので、メモリ開放やスレッド終了処理の詳細な挙動が気になり、本番で使うには不安があります。Pythonではどうなってるか分からないのですが、Cだとdetatchしてないスレッドがメモリに残り続けるとか色々あるので... おそらく、 ・del によりオブジェクトへの参照がなくなったのでGCの対象になった ・GCされたので、ソケットの受信待ち(ブロッキング)状態を貫通してスレッドが破壊された って感じだと思うのですが、正確な挙動をシミュレーションできる方はいらっしゃいますでしょうか? お行儀の良いスレッドの停止方法は、イベントを設定してスレッド内で監視...というのは一応知っています。ですが、これをしたとしても、delした瞬間にスレッドが強制終了するなら、イベントの監視を通る前にどっちにしろ壊れてしまいます。 よろしくお願いいたします。
回答: まず、一番最初に言わなければならないことは`del`は変数や配列等の要素の削除であって、**オブジェクトの削除ではありません**。変数の場合はオブジェクトへのアクセスにその変数が使えなくなるだけ、配列等の要素の削除であれば、要素であったオブジェクトが配列等から除外されるだけであり、オブジェクト自体がなくなるというわけではありません。オブジェクトへの参照が一つ減ることになるため、そのオブジェクトの参照カウントが0になればオブジェクトも同時に削除されますが、他からも参照されていれば、`del`によって削除されることはありません。 ここまでは**十分**わかっていますよね。では、スレッドが実行されているとき、スレッドオブジェクトを参照しているのは本当にその変数だけなのでしょうか? ``` import sys import threading import time def thread_run(): print('therad start') time.sleep(10) print('thread end') t = threading.Thread(target=thread_run) print('t ref count: %d' % sys.getrefcount(t)) t.start() print('t ref count: %d' % sys.getrefcount(t)) time.sleep(20) print('t ref count: %d' % sys.getrefcount(t)) ``` `sys.getrefcount()`はオブジェクトの参照カウント数を取得する関数です。関数内部でも参照しているため、常に1多いことに注意してください。上記を実行してみればわかりますが、`t.start()`を実行後に`t`に対する参照カウント数が増えます(Python2では2->10にPython3では2->9に)。そして、スレッドが終わったであろう頃に再度確認すると参照カウント数はもとに戻ります。 詳しいところまで追っていませんが、スレッドがアクティブに実行しているときは、それらスレッドを管理するところでスレッドが参照されているという仕組みのようです。そう言った仕組みでなければ、`threading.active_count()`でアクティブなスレッドの数を数えるなどできるはずもありませんので、当たり前と言えば当たり前かも知れません。つまり、スレッドがアクティブであるうちはいくら`del t`等で参照カウント数を一つ減らしても、スレッドオブジェクト自体の参照カウント数は0になることはなく、スレッドが破棄されることは無いと言うことです。 ということで、`del`では既に動作しているスレッドは停止もしませんし、破棄もされません。そのまま動き続けます。そして、スレッドが終了すれば、参照もなくなるため、(他に参照しているものがなければ)参照カウント数が0になってオブジェクトが破棄されます。 スレッドを強制的に止めたい場合はどうするかですが、現在の所、Pythonのthreadingの実装では、スレッドを外部から停止する方法はないようです。スレッドがループになっていれば、スレッド外部の変数をフラグにして次のループの実行を止めると言うぐらいが現状のようです。multiprocessingであれば別プロセスなので、プロセスにSIGTERM送るとかの手段はあるようです。調べると色々サンプルがでてきますので、"python thread stop"などのキーワードでググってみてください。