2016-09-01 3 views
1

Что такое метод обновления члена класса в Python, пока он все еще используется другими методами в классе?Threading of Periodic Update to Class Member

Я хочу, чтобы остальная часть класса продолжала обработку с использованием старой версии этого элемента, пока он не будет полностью обновлен, а затем после завершения обновления переключите всю обработку на новую версию.

Вот игрушка пример, чтобы проиллюстрировать мой случай использования, где self.numbers является членом класса, который нуждается в безопасном резьбовой периодическое обновление с помощью логики в updateNumbers(), которую я хочу назвать в неблокируемом способе по runCounter().

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() 

Обратите внимание, что я не использовал никаких замков :). Я мог уйти без использования каких-либо блокировок, потому что я получаю доступ только к атрибуту numbers класса SimpleUpdater с использованием атомных операций, в данном случае просто простых назначений. Каждое присваивание self.numbers связано с новым объектом Numbers с этим атрибутом. Каждый раз, когда вы получаете доступ к этому атрибуту, вы должны ожидать получить другой объект, но если вы берете локальную ссылку на этот объект в начале метода, то есть numbers = self.numbers, numbers всегда будет ссылаться на тот же объект (пока он не выходит за рамки в конце метода), даже если обновление фонового потока self.numbers.

Причина, по которой я создал класс Numbers, заключается в том, что я могу получить и установить все «изменчивые» элементы в одном (атомарном) назначении (список чисел и 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