我是python的新手,目前正在尝试学习线程。我厌倦了使用锁来使我的资源线程安全,因为它们本身并不绑定到资源,所以每次我的代码与资源交互时,我一定会忘记获取和/或释放它们。相反,我希望能够“包装”(或装饰?)一个对象,这样它的所有方法和属性getter/setter都是原子的。如下所示:
state = atomicObject(dict())
# the following is atomic/thread-safe
state["some key"] = "some value"
这个是可能的吗?如果是这样,实现它的“最佳实践”方式是什么?
编辑:上述问题的一个很好的答案可以在How to make built-in containers (sets, dicts, lists) thread safe?中找到。然而,正如abarnert和jsbueno所展示的那样,我提出的解决方案(自动化锁)通常不是一个好主意,因为确定原子操作的适当粒度需要一些智能,并且很可能很难(或不可能)正确地实现自动化。
问题仍然存在,锁没有以任何方式绑定到它们应该保护的资源,所以我的新问题是:什么是将锁与对象关联的好方法?
建议的解决方案#2:我想可能有一种方法可以将锁绑定到对象,这样在没有首先获取锁的情况下尝试访问该对象会抛出错误,但我可以看到这是如何变得棘手的。
编辑:以下代码与问题不太相关。我发布它是为了证明我曾试图自己解决这个问题,但在发布这个问题之前迷失了方向。
根据记录,我编写了以下代码,但它不起作用:
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)
我可以创建一个atomicObject(dict()),但是当我尝试向对象添加值时,我得到错误消息:"atomicObject不支持项分配“。
发布于 2013-04-12 09:45:03
我在你的问题上花了一些心思,这将是一个棘手的问题-你不仅要用你的原子类代理所有的对象方法,这可以通过正确地编写__getattribute__
方法来完成-但是为了让操作符本身工作,你还必须给被代理的对象提供一个类,这个类提供了与原始的对象类相同的“魔术双下划线”方法-也就是说,你必须动态地创建一个代理类-否则操作符的使用本身就不是原子的。
这是可行的-但由于您是Python的新手,您可以在交互提示符上执行import this
,并且在出现的几个指南/建议中,您将看到:“如果实现很难解释,这不是一个好主意。”:-)
这就引出了一个问题:在Python中使用线程通常不是一个好主意。除了带有大量阻塞I/O的准平凡代码--你会更喜欢另一种方法--Python线程化不允许普通-as代码使用更多的CPU核,例如--一次只有一个Python代码线程在运行--搜索"Python GIL“来了解原因-(例外,如果你的大量代码都花在计算密集型的本机代码上,比如Numpy函数)。
但是您更愿意使用各种可用的框架来编写程序来使用异步调用,或者为了更容易地利用多个内核,使用multiprocessing
而不是threading
-它基本上为每个“线程”创建一个进程-并且要求所有的共享都是显式完成的。
发布于 2019-05-31 10:15:26
发布于 2013-04-12 10:25:07
尽管我的另一个答案-它有关于Python线程的合理考虑,并将现有对象转换为“原子”锁定对象-但如果您定义要原子锁定的对象的类,整个事情就简单了一个数量级。
你可以做一个函数装饰器,让函数用四行锁来运行。这样就可以构建一个类装饰器,它自动锁定给定类的所有方法和属性。
下面的代码适用于Python 2和3(我在函数调用中使用了@abarnet的示例,在类示例中依赖于我的“打印调试”。)
import threading
from functools import wraps
#see http://stackoverflow.com/questions/15960881/how-to-decorate-a-python-object-with-a-mutex/15961762#15960881
printing = False
lock = threading.Lock()
def atomize(func):
@wraps(func)
def wrapper(*args, **kw):
with lock:
if printing:
print ("atomic")
return func(*args, **kw)
return wrapper
def Atomic(cls):
new_dict = {}
for key, value in cls.__dict__.items():
if hasattr(value, "__get__"):
def get_atomic_descriptor(desc):
class Descriptor(object):
@atomize
def __get__(self, instance, owner):
return desc.__get__(instance, owner)
if hasattr(desc, "__set__"):
@atomize
def __set__(self, instance, value):
return desc.__set__(instance, value)
if hasattr(desc, "__delete__"):
@atomize
def __delete__(self, instance):
return desc.__delete__(instance)
return Descriptor()
new_dict[key] = get_atomic_descriptor(value)
elif callable(value):
new_dict[key] = atomize(value)
else:
new_dict[key] = value
return type.__new__(cls.__class__, cls.__name__, cls.__bases__, new_dict)
if __name__ == "__main__": # demo:
printing = True
@atomize
def sum(a,b):
return a + b
print (sum(2,3))
@Atomic
class MyObject(object):
def _get_a(self):
return self.__a
def _set_a(self, value):
self.__a = value + 1
a = property(_get_a, _set_a)
def smurf(self, b):
return self.a + b
x = MyObject()
x.a = 5
print(x.a)
print (x.smurf(10))
# example of atomized function call - based on
# @abarnet's code at http://pastebin.com/MrtR6Ufh
import time, random
printing = False
x = 0
def incr():
global x
for i in range(100):
xx = x
xx += 1
time.sleep(random.uniform(0, 0.02))
x = xx
def do_it():
threads = [threading.Thread(target=incr) for _ in range(20)]
for t in threads:
t.start()
for t in threads:
t.join()
do_it()
print("Unlocked Run: ", x)
x = 0
incr = atomize(incr)
do_it()
print("Locked Run: ", x)
注意:虽然"eval“和"exec”在Python中可用,但严肃的代码很少--我是说很少--也不需要。即使是重新创建函数的复杂装饰器也可以通过内省进行dos,而不是依赖于通过eval进行字符串编译。
https://stackoverflow.com/questions/15960881
复制相似问题