質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

3回答

7023閲覧

Python: リスト内のスレッドオブジェクトを del したときの挙動について

nyanchan

総合スコア6

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2017/06/02 01:03

Python2.7 初心者です。
サーバープログラムを書いています。TCPコネクションを管理している複数のスレッドオブジェクト(threading.thread)を一つのリストに格納しています。クライアントから切断されたときに、即座にソケットを破棄して、リストからも削除したいと思っています。
現在は、スレッドオブジェクトへの参照を持つリスト要素を直接delメソッドで削除しています。これでも動作は正常で、スレッド自体は終了しているように見えます。ですが、スレッド内のreturnを経由している気配がないので、メモリ開放やスレッド終了処理の詳細な挙動が気になり、本番で使うには不安があります。Pythonではどうなってるか分からないのですが、Cだとdetatchしてないスレッドがメモリに残り続けるとか色々あるので…
おそらく、
・del によりオブジェクトへの参照がなくなったのでGCの対象になった
・GCされたので、ソケットの受信待ち(ブロッキング)状態を貫通してスレッドが破壊された
って感じだと思うのですが、正確な挙動をシミュレーションできる方はいらっしゃいますでしょうか?
お行儀の良いスレッドの停止方法は、イベントを設定してスレッド内で監視…というのは一応知っています。ですが、これをしたとしても、delした瞬間にスレッドが強制終了するなら、イベントの監視を通る前にどっちにしろ壊れてしまいます。
よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

まず、一番最初に言わなければならないことはdelは変数や配列等の要素の削除であって、オブジェクトの削除ではありません。変数の場合はオブジェクトへのアクセスにその変数が使えなくなるだけ、配列等の要素の削除であれば、要素であったオブジェクトが配列等から除外されるだけであり、オブジェクト自体がなくなるというわけではありません。オブジェクトへの参照が一つ減ることになるため、そのオブジェクトの参照カウントが0になればオブジェクトも同時に削除されますが、他からも参照されていれば、delによって削除されることはありません。

ここまでは十分わかっていますよね。では、スレッドが実行されているとき、スレッドオブジェクトを参照しているのは本当にその変数だけなのでしょうか?

Python

1import sys 2import threading 3import time 4 5def thread_run(): 6 print('therad start') 7 time.sleep(10) 8 print('thread end') 9 10t = threading.Thread(target=thread_run) 11 12print('t ref count: %d' % sys.getrefcount(t)) 13t.start() 14print('t ref count: %d' % sys.getrefcount(t)) 15time.sleep(20) 16print('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"などのキーワードでググってみてください。

投稿2017/06/03 04:37

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

まず、スレッドにはネイティブスレッドとグリーンスレッドというものがあります。
ネイティブスレッドはOSのスレッド機能を使い、グリーンスレッドはVMがスレッドを独自で管理します。
Pythonはネイティブスレッドですので、スレッドの管理方法はOSに依存します。

って感じだと思うのですが、正確な挙動をシミュレーションできる方はいらっしゃいますでしょうか?

OSの関係、Pythonバージョンの関係、GCの関係などが絡み合い、正確な挙動は予測不可能だと思います。

まず、スレッドの破棄をdelに頼っている設計が根本的に間違っているのではないでしょうか?
GCはdelされた瞬間にオブジェクトを破棄するとは限りませんし、どこかの誰かが参照していたら破棄されません。

そのような設計は、非常に脆弱で壊れやすく保守もしにくいと思います。
リークが怖いのであれば、weakrefモジュールを使うと良いでしょう。
私の記事で申し訳ありませんが、以下の記事を参考にしてください。

http://qiita.com/pashango2/items/fb1e5e79589279c5a861

また蛇足ですが__del__メソッドを定義することはおすすめしません。
PythonのGCは__del__メソッドがあると、循環参照の解放をしなくなります。

投稿2017/06/02 23:22

編集2017/06/02 23:29
pashango2

総合スコア930

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

Pythonのバージョンによっても若干挙動が違うと思いますが、threading.Threaddelしても(そこでの参照を消しても)即スレッドが破壊される事のない様な管理方法に内部ではなっています。threading.active_count()の返す値を元に現在のインタプリタが保持しているスレッド数を見てみると良いと思います。delしただけではカウントは減りません。

投稿2017/06/02 01:46

YouheiSakurai

総合スコア6142

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YouheiSakurai

2017/06/02 03:35

申し訳ないのです、嘘をつきました。以下のコード、Python 3.5相当のPythonista2環境でしばらくするとカウントが減りました。 from threading import Thread from threading import active_count import time t = Thread(target=time.sleep, args=(10,)) t.start() del t for _ in range(10): print(active_count()) time.sleep(1)
YouheiSakurai

2017/06/02 03:39

いや、嘘じゃなかったです。以下のコードで2->3->3->...となりました。 from threading import Thread from threading import active_count import time t = Thread(target=time.sleep, args=(10,)) print(active_count()) t.start() del t for _ in range(10): print(active_count()) time.sleep(1) なんかやっぱ実装環境によって挙動が異なるみたいです。。。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問