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

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

ただいまの
回答率

88.10%

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

受付中

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 3,379

score 6

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+1

まず、一番最初に言わなければならないことは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"などのキーワードでググってみてください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/02 12: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)

    キャンセル

  • 2017/06/02 12: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)

    なんかやっぱ実装環境によって挙動が異なるみたいです。。。

    キャンセル

0

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

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

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

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

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 88.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る