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

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

ただいまの
回答率

90.51%

  • Python 3.x

    9841questions

    Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

  • Django

    1614questions

    DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

[Django] 孫スレッドを即時停止させる方法

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 2,356

horik

score 36

Djangoであるボタンを押すと内部にスレッドを持つ子スレッドを実行させます。
実行して10秒後に停止処理をいれたとき、
子スレッドが停止しているのにも関わらず孫スレッドが最後まで処理を継続してしまいます。

単体で実行したら想定通り子スレッドが停止したら孫スレッドも停止します。
Djangoでも同じような挙動にするにはどうすればいいでしょうか?

sample_thread.pyを単体で実行した場合のログ

MainThread: ThreadTwo実行
ThreadTwo: BotClass実行
ThreadThree: 0
ThreadFour: 0
ThreadThree: 1
ThreadFour: 1
ThreadThree: 2
ThreadThree: 3
ThreadFour: 2

Process finished with exit code 0

 djangoでview経由で実行した場合のログ

Thread-1: ThreadTwo実行
ThreadTwo: BotClass実行
ThreadThree: 0
ThreadFour: 0
ThreadThree: 1
ThreadThree: 2
ThreadFour: 1
ThreadThree: 3
ThreadFour: 2
Dummy-4: ThreadTwo終了
ThreadThree: 4
[20/Dec/2016 16:21:40] "GET /sample/ HTTP/1.1" 200 6
ThreadFour: 3
ThreadFour: 4
ThreadFour: BotClass終了

 views.py

def sample(request):
    th2 = ThreadTwo(name='ThreadTwo')
    th2.start()
    time.sleep(10)
    th2.stop()
    return HttpResponse('sample')

 sample_thread.py

import threading
import time


class ThreadTwo(threading.Thread):
    def __init__(self, name):
        super(ThreadTwo, self).__init__(name=name)
        print(threading.currentThread().getName() + ': ThreadTwo実行')
        self.botclass = BotClass()

    def __del__(self):
        print(threading.currentThread().getName() + ': ThreadTwo終了')

    def run(self):
        print(threading.currentThread().getName() + ': BotClass実行')
        self.botclass.listen()

    def stop(self):
        self.botclass.listening = False


class BotClass:
    def __init__(self):
        self.listening = True

    def __del__(self):
        print(threading.currentThread().getName() + ': BotClass終了')

    def listen(self):
        th3 = threading.Thread(target=self._thread_three, name='ThreadThree')
        th3.setDaemon(True)
        th3.start()
        th4 = threading.Thread(target=self._thread_four, name='ThreadFour')
        th4.setDaemon(True)
        th4.start()
        while self.listening:
            # TODO 何かしらの待ち受け処理
            pass

    def _thread_three(self):
        for i in range(5):
            time.sleep(2)
            print(threading.currentThread().getName() + ': ' + str(i))

    def _thread_four(self):
        for i in range(5):
            time.sleep(3)
            print(threading.currentThread().getName() + ': ' + str(i))


if __name__ == '__main__':
    th2 = ThreadTwo(name='ThreadTwo')
    th2.start()
    time.sleep(10)
    th2.stop()
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

0

こんにちは。自分もちょうど並行処理調べてたので、何かしらヒントになれば。

基本的事項として、オライリーの入門Python3本によるとThreadは強制停止しにくいらしいです。(p335)
また、Python公式Documentに記載してある、以下の事を念頭に置いておくと良い感じがします。

現状では、優先度 (priority) やスレッドグループがなく、スレッドの破壊 (destroy)、中断 (stop)、一時停止 (suspend)、復帰 (resume)、割り込み (interrupt) は行えません。

単体で実行した場合の挙動

単体で実行したら想定通り子スレッドが停止したら孫スレッドも停止します。 

とのことですが、以下のコードにすると、引き続き孫Threadが動作していました。stop()が機能していないように思えます。

# sample_thread.py
(前略)
if __name__ == '__main__':
    th2 = ThreadTwo(name='ThreadTwo')
    th2.start()
    time.sleep(10)
    th2.stop()
    time.sleep(100) # 追記 / stopで孫threadが止まるはずなのに、実行続く

元々のコードでは、main終了時に、デーモンスレッドだけになる=孫threadが停止し、プログラム全体が終了というフローになっていたと考えます。公式Document

スレッドには “デーモンスレッド (daemon thread)” であるというフラグを立てられます。 このフラグには、残っているスレッドがデーモンスレッドだけになった時に Python プログラム全体を終了させるという意味があります。フラグの初期値はスレッドを生成したスレッドから継承します。フラグの値は daemon プロパティまたは daemon コンストラクタ引数を通して設定できます。

Django上で実行したものはstop()が機能していないのでthreadが止まらず、また、django本体のスレッドは停止していない(?)ので、特にデーモンもkillされず、ThreadTwo終了/ BotClass終了まで処理が進んでしまっていたと考えます。

対策としては、threadではなくmultiprocessingを使うと良いかもしれません。terminate()関数により強制停止が可能なようです。(あまり知識がないので、もっと良い方法があるかもしれませんが…)

(追記)stop()の実装

やりたいこととぜんぜん違うかもしれませんが、以下でstop的な動作になりました。
threadをkillする手段が無い以上、worker(thread)内部に停止コード仕込むしかない、と考えます。
Qiitaでもpythonのthread記事は荒れてました。

class BotClass:
# (前略)
    def _thread_three(self):
        for i in range(5):
            if not self.listening:  # add 
                break # add 
            time.sleep(2)
            print(threading.currentThread().getName() + ': ' + str(i))

    def _thread_four(self):
        for i in range(5):
            if not self.listening: # add
                break # add
            time.sleep(3)
            print(threading.currentThread().getName() + ': ' + str(i))

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/01/06 09:10

    丁寧な回答ありがとうございます!
    multiprocessingとbreakで抜ける方法両方で試してみます!

    さらにceleryというものもあるらしいので試してみようと思います。

    キャンセル

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

  • Python 3.x

    9841questions

    Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

  • Django

    1614questions

    DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。