Python中的线程安全对象和字典类型

1 投票
1 回答
1774 浏览
提问于 2025-04-17 05:09

我有两个关于在Python中创建线程安全类型的问题,还有一个关于多重继承的相关问题。

1) 在我的多线程应用中,使用以下子类作为一种“懒惰”的线程安全类型,会有什么问题吗?我明白,任何设置可能被其他线程修改的值的人,都有责任确保这些值是线程安全的。

2) 我还有一个问题,就是在普通的Python安装中,是否存在更好的替代这些类型的选择。

示例:

from threading import Lock
from __future__ import with_statement

class safedict(dict):
    def __init__(self,*args,**kwargs):
        self.mylock=Lock(); 
        super(safedict, self).__init__(*args, **kwargs)
    def __setitem__(self,*args,**kwargs):
        with self.mylock:
            print " DEBUG: Overloaded __setitem__ has the lock now."
            super(safedict,self).__setitem__(*args,**kwargs)


class safeobject(object):
    mylock = Lock(); # a temporary useless lock, until we have a proper instance.
    def __init__(self,*args,**kwargs):
        self.mylock=Lock(); 
        super(safeobject, self).__init__(*args, **kwargs)
    def __setattr__(self,*args,**kwargs):
        with self.mylock:
            print " DEBUG: Overloaded __setattr__ has the lock now."
            super(safeobject,self).__setattr__(*args,**kwargs)

3) 如果上面定义的两种类型都可以被认为是相对安全的,使用多重继承来创建一个支持这两种修改混合的类型,会面临什么负面影响?我的示例中继承这些类的顺序是否最优?

示例:

class safedict2(safeobject,dict):
    def __setitem__(self,*args,**kwargs):
        with self.mylock:
            print " DEBUG: Overloaded __setitem__ has the lock now."
            super(safedict2,self).__setitem__(*args,**kwargs)

编辑:

再给一个示例,展示另一个类型同时继承前面两种类型,并使用ipython进行测试。

In [304]: class safedict3(safeobject,safedict):
   .....:         pass
   .....: 

In [305]: d3 = safedict3()
 DEBUG: Overloaded __setattr__ has the lock now.
 DEBUG: Overloaded __setattr__ has the lock now.

In [306]: d3.a=1
 DEBUG: Overloaded __setattr__ has the lock now.

In [307]: d3['b']=2
 DEBUG: Overloaded __setitem__ has the lock now.

In [308]: d3
Out[308]: {'b': 2}

1 个回答

1

关于你提的第一个和第二个问题,像 dictlist 这些类型本身就是线程安全的。你不需要再给它们添加线程安全的功能。不过,你可能会觉得下面这个东西有用。它是一个装饰器,基本上实现了 Java 中的 synchronized 关键字,利用函数的作用域来定义一个关键区域。用类似的方法,你也可以写一个基于 threading.Condition 的装饰器。

import threading

def tryfinally(finallyf):
  u"returns a decorator that adds try/finally behavior with given no-argument call in the finally"
  def decorator(callable):
    def execute(*args, **kwargs):
      try: result = callable(*args, **kwargs)
      finally: finallyf()
      return result
    return execute
  return decorator

def usinglock(lock):
  u"returns a decorator whose argument will acquire the given lock while executing"
  def decorator(function):
    body = tryfinally(lock.release)(function)
    def execute(*args, **kwargs):
      lock.acquire()
      return body(*args, **kwargs)
    return execute
  return decorator

def synchronized(function):
  u"decorator; only one thread can enter the decorated function at a time; recursion is OK"
  return usinglock(threading.RLock())(function)

使用方法是这样的(不过要小心,如果用得过多可能会导致死锁):

@synchronized
def foo(*args):
  print 'Only one thread can enter this function at a time'

关于第三个问题,Python 教程中提到,继承属性的查找顺序是先深后浅,左先右后。所以如果你继承了 (myclass, dict),那么会使用 myclass__setitem__ 方法。(在旧版本的 Python 中,这部分内容暗示这个选择是任意的,但现在看来这个选择是很有意图的。)

我猜从你发的代码中出现的分号来看,你可能是刚接触 Python,但在 Java 或 C# 上有经验。如果是这样,你需要记住,Python 中属性(方法)的解析是在运行时进行的,而且 和实例都是可以在运行时被检查和探索的一等对象。

首先会查找 实例 的属性字典,然后查找 的属性,最后才是父类的查找算法。这一过程可以想象成反复调用 hasattr(class_or_instance, attribute)

下面的内容确认了对于“新式”类(即从 object 继承的类,在 2.x 语言规范中是可选的),每次查找属性时都会进行这种解析。并不是在类(或子类)创建时,或者在实例创建时就完成了。(这在 2.7.2 版本中是这样处理的。)

>>> class Foo(object):
...   def baz(self):
...     print 'Original Foo.baz'
...
>>> class Bar(Foo): pass
...
>>> def newprint(self):
...   print 'New Foo.baz'
...
>>> x = Foo()
>>> y = Bar()
>>> Foo.baz = newprint
>>> a = Foo()
>>> b = Bar()
>>> map(lambda k: k.baz(), (x, y, a, b))
New Foo.baz
New Foo.baz
New Foo.baz
New Foo.baz
[None, None, None, None]

替换类 Foo 的方法会改变已经定义的子类和已经创建的实例的行为。

撰写回答