2016-09-01 6 views
1

クラス内の他のメソッドがまだ使用している間に、クラスメンバーをPythonで更新する方法とは何ですか?クラスメンバーへの定期的な更新のスレッド

完全に更新されるまでメンバーの古いバージョンを使用して残りのクラスを処理し続け、更新が完了するとすべての処理を新しいバージョンに切り替えます。ここで

self.numbersは、私がrunCounter()により、非ブロッキング方法で呼び出さたいupdateNumbers()のロジックを使用して安全にスレッド定期的に更新し、必要とするクラスのメンバーである私のユースケースを、説明するためにおもちゃの一例です。

from time import sleep, time 

class SimpleUpdater(object): 
    def __init__(self): 
     self.i = 5 
     self.numbers = list(range(self.i)) 
     self.lastUpdate = time() 
     self.updateDelta = 10 

    def timePast(self): 
     now = time() 
     delta = self.lastUpdate - now 
     return (delta > self.updateDelta) 

    def updateNumbers(self): 
     print('Starting Update', flush=True) 
     self.numbers = list(range(self.i)) 
     # artificial calculation time 
     sleep(2) 
     print('Done Updating', flush=True) 

    def runCounter(self): 
     for j in self.numbers: 
      print(j, flush=True) 
      sleep(0.5) 
     self.i += 1 
     if self.timePast: 
      ## Spin off this calculation!! (and safely transfer the new value) 
      self.updateNumbers() 

if __name__ == '__main__': 
    S = SimpleUpdater() 
    while True: 
     S.runCounter() 

望ましい行動はself.numbersがループでの反復されている場合、それは新しいバージョンに切り替える前に古いバージョンでループを終了しなければならないことです。

+0

おそらくあなたのデザインを再考する必要があるようです。いずれにしても、ここで「安全な」行動とは何かを判断するための十分な情報は私たちにはありませんでした。たとえば、あるスレッドが 'self.numbers'をループしていて、別のスレッドがループの途中で' self.numbers'を更新した場合、ループは新しいバージョンをループするように切り替えるべきですか? – user2357112

+0

@ user2357112いいえ、新しいバージョンに切り替える必要はありません。ループが終了するまで古いものを使用し続ける必要があります。 –

答えて

1

updateNumbersを呼び出すたびに新しいスレッドを作成できますが、このタイプの処理を行う一般的な方法は、バックグラウンドで無限ループを1スレッド実行することです。その無限ループを持つメソッドまたは関数を記述する必要があり、そのメソッド/関数はバックグラウンドスレッドのターゲットとして機能します。この種のスレッドはしばしばデーモンですが、必ずしもそうである必要はありません。私はあなたのコードを修正して、これがどのように行われるかを示しました(私はあなたのサンプルコードにいくつかの小さなバグも修正しました)。

from time import sleep, time 
import threading 


class Numbers(object): 
    def __init__(self, numbers): 
     self.data = numbers 
     self.lastUpdate = time() 


class SimpleUpdater(object): 
    def __init__(self): 
     self.i = 5 
     self.updateDelta = 5 
     self.numbers = Numbers(list(range(self.i))) 
     self._startUpdateThread() 

    def _startUpdateThread(self): 
     # Only call this function once 
     update_thread = threading.Thread(target=self._updateLoop) 
     update_thread.daemon = True 
     update_thread.start() 

    def _updateLoop(self): 
     print("Staring Update Thread") 
     while True: 
      self.updateNumbers() 
      sleep(.001) 

    def updateNumbers(self): 
     numbers = self.numbers 
     delta = time() - numbers.lastUpdate 
     if delta < self.updateDelta: 
      return 
     print('Starting Update') 
     # artificial calculation time 
     sleep(4) 
     numbers = Numbers(list(range(self.i))) 
     self.numbers = numbers 
     print('Done Updating') 

    def runCounter(self): 
     # Take self.numbers once, then only use local `numbers`. 
     numbers = self.numbers 
     for j in numbers.data: 
      print(j) 
      sleep(0.5) 
     # do more with numbers 
     self.i += 1 


if __name__ == '__main__': 
    S = SimpleUpdater() 
    while True: 
     S.runCounter() 

私はロックを使用していないことに注意してください。アトミック操作を使用してSimpleUpdaterクラスのnumbersアトリビュートにアクセスするだけなので、ロックを使用せずに離れることができました。この場合は単純な割り当てです。 self.numbersへのすべての割り当ては、その属性を持つ新しいNumbersオブジェクトを関連付けました。その属性にアクセスするたびに別のオブジェクトを取得する必要がありますが、メソッドの開始時にそのオブジェクトにローカル参照を取ると、numbers = self.numbersのように、numbersは常に同じオブジェクトを参照します(範囲外になるまで)メソッドの最後に)、たとえバックグラウンドスレッドがself.numbersを更新したとしても。

私がNumbersクラスを作成したのは、すべての "volatile"メンバーを単一のアトミックな割り当て(数値リストとlastUpdated値)で取得して設定できるからです。私はこれが追跡しておくべきことが多いことを知っています。正直なところロックを使用するだけでもっとスマートで安全かもしれません:)しかし、私はあなたにもこのオプションを示したかったのです。

+0

Pythonの割り当ては* not * atomic、btwです。私は[この回答](http://stackoverflow.com/questions/2623086/is-a-variable-swap-guaranteed-to-be-atomic-in-python/2623117#2623117)のコードで確認しました。このコードでは、割り当てのための 'RLock'メンバーを追加しますが、最悪の結果は古いバージョンのデータ構造を使用しているようです。 –

+1

あなたは正しいです@ChrisRedford、割り当てはアトミックであることが保証されていません、それはちょうどこの場合、割り当てはアトミックです。割り当てがアトミックであることがわからない場合は、常にロックを使用する必要があります。 –

1

あなたのリストへのアクセスを制御するためにロックを作成します。

import threading 

def __init__(self, ...): 
    # We could use Lock, but RLock is somewhat more intuitive in a few ways that might 
    # matter when your requirements change or when you need to debug things. 
    self.numberLock = threading.RLock() 
    ... 

あなたは、リストを読んでロックを保持し、ローカル変数に現在のリストを保存する必要があるとき。ローカル変数は、インスタンス属性への更新の影響を受けません。あなたは、リストを更新ロックを保持し、古いリストを変異させずに、新しいリストとリストを交換する必要があるとき

with self.numberLock: 
    numbers = self.numbers 
doStuffWith(numbers) 

:あなたが更新された値をチェックするまで、ローカル変数を使用

with self.numberLock: 
    self.numbers = newNumbers 

ちなみに、私はあなたのコードを一致させるためにここにキャメルケースを使用しましたが、Pythonの規約は変数と関数名のためにlowercase_with_underscoresの代わりcamelCaseを使用することです。

+0

これは答えの重要な部分をカバーしています。また、 'updateNumbers()'(これは 'numberLock'と組み合わせて)を呼び出して、' runCounter() 'が更新中にカウントし続けるようにする方法についてのいくつかの洞察を与えることができますか? –

+0

@ChrisRedford: 'updateNumbers'呼び出しを別のスレッドで実行します。 – user2357112

関連する問題