定期更新对类成员的线程

时间:2016-09-01 23:17:30

标签: python multithreading

在类中的其他方法仍然使用它时,在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,它应该在切换到新版本之前完成旧版本的循环。

2 个答案:

答案 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.numbersnumbers将始终引用相同的对象。 object(直到它超出方法结束时的范围),即使后台线程更新self.numbers

我创建Numbers课程的原因是我可以获取并设置所有&#34; volatile&#34;单个(原子)赋值中的成员(数字列表和lastUpdated值)。我知道要记住这一点很多,老实说,只使用锁:)可能更聪明,更安全,但我也想向您展示这个选项。