0

I have defined a class, Test, one of the methods of which, updater, is run in a separate thread and continuously updates the property, steps. I am hoping for some definitive advice regarding safe modification of the property, and safe access of the property in other threads.

Below is some working code that fulfils my requirements, making liberal use of a thread lock as suggested here. I have deployed the lock in both thread, and get and set methods since operations on the property steps in all are non-atomic (or most likely will be in my finished code).

from threading import Thread, Lock
from time import sleep


class Test:
    def __init__(self):
        self.lock = Lock()
        self.steps = 5
        self.thread = Thread(target=self.updater)
        self.threadRun = True
        self.thread.start()

    def updater(self):
        while self.threadRun:
            with self.lock:
                self.steps += 1
            sleep(0.001)
        print('Thread stopped')

    def get_steps(self):
        with self.lock:
            return self.steps

    def set_steps(self, steps):
        with self.lock:
            self.steps = steps

I understand that accessing instance properties directly is preferred over using getter and setter methods:

a = Test()
steps = a.steps
a.steps = 5

rather than:

a = Test()
steps = a.get_steps()
a.set_steps(5)

but I have used them here so I can include the lock. I am not certain of the atomicness of direct access of the property in this case so might be able to avoid the lock, but can someone confirm if this is the case? If I still have to use the lock, and use getter and setter methods for each of the properties I want to access, how do I deploy a decorator within the class to make this smarter when I scale up to multiple properties? I expect something like this:

from threading import Thread, Lock
from time import sleep


class Test:
    def __init__(self):
        self.lock = Lock()
        self.steps = 5
        self.thread = Thread(target=self.updater)
        self.threadRun = True
        self.thread.start()

    def updater(self):
        while self.threadRun:
            with self.lock:
                self.steps += 1
            sleep(0.001)
        print('Thread stopped')

    def locker(self, func):
        with self.lock:
            return func

    @locker
    def get_steps(self):
        return self.steps

    @locker
    def set_steps(self, steps):
        self.steps = steps

But at this point my skill at argument handling between classes and decorators comes up short and I get:

TypeError: locker() missing 1 required positional argument: 'func'

Some insight is here, but as per the first comment on the solution, I need to deal properly with arguments. I suppose I could put the locker function outside the class, but I was hoping to contain it within the class for neatness - I would not use it elsewhere. What's an elegant solution here?

Alex
  • 33
  • 7
  • If the thread (`updater`) has a non-atomic operation on property `steps` will I need to use a lock to access `steps` regardless of whether the direct access operation is atomic or not? – Alex Apr 03 '19 at 13:18

1 Answers1

0

Ok, after some help from this question, I have a workable solution:

from threading import Thread, Lock
import time


class Blah():
    def __init__(self):
        self.lock = Lock()
        self.a = 0
        self.thread = Thread(target=self.updater)
        self.threadRun = True
        self.thread.start()

    def locker(func):
        def magic(self, *args):
            with self.lock:
                print('locked')
                return func(self, *args)
        return magic

    def updater(self):
        t_start = time.time()
        while self.threadRun and time.time() - t_start <= 20:
            self.a += 1
            time.sleep(2)
        print('Thread stopped')

    @property
    @locker
    def a(self):
        return self._a

    @a.setter
    @locker
    def a(self, val):
        self._a = val

I am now trying to apply this approach to many of the instance attributes, not just a and I'm looking for a pythonic/dynamic way to do this other than having two discrete blocks of code (@property and @attr.setter) for each. Ideally I would initialise each attribute in the __init__ constructor, which would both assign an initial value and setup the @property and @attr.setter methods, appropriately wrapped with the @locker. Can anyone suggest how to do this?

Alex
  • 33
  • 7