I'm new to python and am currently trying to learn threading. I'm weary of using locks to make my resources thread-safe because they aren't inherently tied to the resource, so I'm bound to forget to acquire and/or release them every time my code interacts with the resource. Instead, I'd like to be able to "wrap" (or decorate?) an object so that all of it's methods and attribute getters/setters are atomic. something like this:
state = atomicObject(dict())
# the following is atomic/thread-safe
state["some key"] = "some value"
Is this possible? If so, what's the "best practices" way of implementing it?
EDIT: An good answer to the above question is available in How to make built-in containers (sets, dicts, lists) thread safe?. However; as abarnert and jsbueno have both demonstrated, the solution I proposed (automating locks) is not generally a good idea because determining the proper granularity of atomic operations requires some intelligence and is likely difficult (or impossible) to automate properly.
The problem still remains that locks are not bound in any way to the resources they are meant to protect, so my new question is: What's a good way to associate a lock with an object?
Proposed solution #2: I imagine there might be a way to bind a lock to an object such that trying to access that object without first acquiring the lock throws an error, but I can see how that could get tricky.
EDIT: The following code is not very relevant to the question. I posted it to demonstrate that I had tried to solve the problem myself and gotten lost before posting this question.
For the record, I wrote the following code, but it doesn't work:
import threading
import types
import inspect
class atomicObject(object):
def __init__(self, obj):
self.lock = threading.RLock()
self.obj = obj
# keep track of function handles for lambda functions that will be created
self.funcs = []
# loop through all the attributes of the passed in object
# and create wrapped versions of each attribute
for name in dir(self.obj):
value = getattr(self.obj, name)
if inspect.ismethod(value):
# this is where things get really ugly as i try to work around the
# limitations of lambda functions and use eval()... I'm not proud of this code
eval("self.funcs.append(lambda self, *args, **kwargs: self.obj." + name + "(*args, **kwargs))")
fidx = str(len(self.funcs) - 1)
eval("self." + name + " = types.MethodType(lambda self, *args, **kwargs: self.atomize(" + fidx + ", *args, **kwargs), self)")
def atomize(self, fidx, *args, **kwargs):
with self.lock:
return self.functions[fidx](*args, **kwargs)
I can create an atomicObject(dict()), but when I try to add a value to the object, I get the error; "atomicObject does not support item assignment".
with
statement, you've got the wrong name for theLock
type, and I have no idea what else might be wrong beyond that. How do you expect us to debug it for you if we can't even get started? – Sapotaceousd = atomicObject(dict())
, thend['abc'] = 3
, thend['abc'] += 1
, that isn't atomic—it atomically readsd['abc']
, then it releases the lock, then it atomically writesd['abc']
, overwriting any other write made in the intervening time. (Imagine thatd
was a counter, and you had 20 threads all trying to do +1 at the same time. Instead of going up +20, it would likely only go up +1 or +2 or so.) – Sapotaceous