While extending existing Python primitives may be the most useful, if not the most desirable, means to provide thread-safe communication, it should be noted that we can also use other techniques.
One of the other key methods that we could leverage is to utilize decorators. With this mechanism, we define our decorator method, locked_method, which takes in a method. This decorator method will then define a new method within it, and call the original method only when it has acquired self._lock.
This allows us to, somewhat effortlessly, turn the potentially erroneous critical sections of our code into thread-safe sections, which can be called without having to worry about race conditions.
In this next example, we look at how we can implement our decorator method that returns a race-condition protected version of our passed in method:
def lock(method):
def newmethod(self, *args, **kwargs):
with self._lock:
return method(self, *args, **kwargs)
return newmethod
class DecoratorLockedSet(set):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(DecoratorLockedSet, self).__init__(*args, **kwargs)
@locked_method
def add(self, *args, **kwargs):
return super(DecoratorLockedSet, self).add(elem)
@locked_method
def remove(self, *args, **kwargs):
return super(DecoratorLockedSet, self).remove(elem)