web-dev-qa-db-ja.com

Pythonでスレッドを再起動する

Python 3.4)のプロジェクトでスレッド化されたフライトソフトウェアを作成しようとしています。この場合、センサーの読み取り中にI/Oエラーが発生した場合や、次のような別のフルーククラッシュが発生した場合にスレッドを再起動する必要があります。したがって、スレッドが停止したかどうかを確認するウォッチドッグを作成し、スレッドを再起動することに取り組んでいます。

最初、私はスレッドがもう存続していないかどうかを確認し、それを再起動しようとしました、これはこれを行いました:

>>> if not a_thread.isAlive():
...     a_thread.start()
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "c:\Python34\lib\threading.py", line 847, in start
    raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once

この動作はthreadingand Python自体の観点からは理にかなっていますが、仕事が難しくなります。そのため、ディクショナリを使用して初期スレッドを保存し、それを新しいオブジェクトを作成し、必要に応じて起動します。残念ながら、これも機能しません。基本的な例を次に示します。

import threading
import logging
import queue
import time
from copy import copy, deepcopy

def a():
    print("I'm thread a")
def b():
    print("I'm thread b")

# Create thread objects
thread_dict = {
'a': threading.Thread(target=a, name='a'),
'b': threading.Thread(target=b, name='b')
}

threads = [copy(t) for t in thread_dict.values()]

for t in threads:
    t.start()
for i in range(len(threads)):
    if not threads[i].isAlive():
        temp = thread_dict[threads[i].name]
        threads[i] = deepcopy(temp)
        threads[i].start()
    thread(i).join(5)

これは次を返します:

I'm thread a
I'm thread b
Traceback (most recent call last):
  File "main_test.py", line 25, in <module>
    threads[i] = deepcopy(temp)
  File "c:\Python34\lib\copy.py", line 182, in deepcopy
    y = _reconstruct(x, rv, 1, memo)
  ... (there's about 20 lines of traceback within copy)
  File "c:\Python34\lib\copyreg.py", line 88, in __newobj__
    return cls.__new__(cls, *args)
TypeError: object.__new__(_thread.lock) is not safe, use _thread.lock.__new__()

明らかにthreadingオブジェクトは安全にコピーできません...とにかく、オブジェクト全体を再作成する前にスレッドを再起動する方法はありますか?

10
skycoop
  1. スレッドを停止させる理由はありません。

それらが実際にクラッシュしている場合、プログラム全体がクラッシュします。

例外が発生しているだけの場合は、例外をキャッチできます。

彼らが正常に戻ってきたら、それはできません。

スレッド関数を簡単にラップして、例外または戻り時に再起動することもできます。

def threadwrap(threadfunc):
    def wrapper():
        while True:
            try:
                threadfunc()
            except BaseException as e:
                print('{!r}; restarting thread'.format(e))
            else:
                print('exited normally, bad thread; restarting')
    return wrapper

thread_dict = {
    'a': threading.Thread(target=wrapper(a), name='a'),
    'b': threading.Thread(target=wrapper(b), name='b')
}    

問題が解決しました。


  1. スレッドを再開することはできません。

ほとんどのプラットフォームには、これを行う方法がありません。

概念的には、意味がありません。スレッドが終了すると、そのスタックは無効になります。その親にはフラグが立てられているか、通知されます。結合されると、そのリソースは破棄されます(プロセステーブルエントリなどのカーネルレベルのリソースを含む)。再起動する唯一の方法は、すべてのまったく新しいセットを作成することです。これは、新しいスレッドを作成することですでに実行できます。

だから、それをしてください。内部で例外を処理したくない場合は、構築引数を保存し、それらを使用して新しいスレッドを開始します。

あなたはあなたのためにそれらにぶら下がるあなた自身のサブクラスを作成することさえできます:

class RestartableThread(threading.Thread):
    def __init__(self, *args, **kwargs):
        self._args, self._kwargs = args, kwargs
        super().__init__(*args, **kwargs)
    def clone(self):
        return RestartableThread(*args, **kwargs)

これで、スレッドを「コピー」することが簡単になりました(必要なセマンティクスで)。

if not a_thread.is_alive():
    a_thread = a_thread.clone()

  1. はい、 threading.Threadオブジェクトはコピーしても安全ではありません

何が起こると思いますか?せいぜい、同じOSレベルのスレッドオブジェクトの別のラッパーを取得するだけなので、Pythonをだまして、不正な、おそらくsegfault-を実行しようとしていることに気付かないようにしますそれがあなたをやめさせようとしていたことを誘発する。

21
abarnert

「figs」が言ったように、スレッドを再起動するのではなく、スレッド内の例外を処理する必要があります。ここの例外のドキュメントを参照してください: https://docs.python.org/2/tutorial/errors.html

そうする方がはるかにシンプルでPythonicです。

2
CoMartel