在类中的其他方法仍然使用它时,在Python中更新类成员的方法是什么?
我希望类的其余部分继续使用旧版本的成员进行处理,直到它完全更新,然后在更新完成后将所有处理切换到新版本。
这是一个玩具示例来说明我的用例,其中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 :(得分: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
顺便说一下,我在这里使用了camelcase来匹配你的代码,但Python的约定是使用lowercase_with_underscores
而不是camelCase
来表示变量和函数名称。
答案 1 :(得分: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
将始终引用相同的对象。 object(直到它超出方法结束时的范围),即使后台线程更新self.numbers
。
我创建Numbers
课程的原因是我可以获取并设置所有&#34; volatile&#34;单个(原子)赋值中的成员(数字列表和lastUpdated值)。我知道要记住这一点很多,老实说,只使用锁:)可能更聪明,更安全,但我也想向您展示这个选项。