如何使用 Python 中的锁来保护对象?

How to protect an object using a lock in Python?

我遇到了需要以下模式的功能:

from threading import Lock

the_list = []
the_list_lock = Lock()

并使用它:

with the_list_lock:
     the_list.append("New Element")

不幸的是,这不需要我获取锁,我可以直接访问对象。我想要一些保护来防止这种情况(我只是人类。)有没有标准的方法来做到这一点?我自己的方法是创建一个 HidingLock class 可以这样使用:

the_list = HidingLock([])
with the_list as l:
    l.append("New Element")

但它感觉非常基础,要么它应该存在于标准库中,要么它是一种非常非常规的锁使用方式。

我目前的解决方案(我在问题中谈到的那个)是这样的:

import threading

class HidingLock(object):
    def __init__(self, obj, lock=None):
        self.lock = lock or threading.RLock()
        self._obj = obj

    def __enter__(self):
        self.lock.acquire()
        return self._obj

    def __exit__(self, exc_type, exc_value, traceback):
        self.lock.release()

    def set(self, obj):
        with self:
            self._obj = obj

下面是如何使用它的:

locked_list = HidingLock(["A"])
with locked_list as l:
    l.append("B")

我认为标准库中没有任何内容的原因是因为它需要做出铸铁访问保证。提供任何不足都会给人一种错误的安全感,这可能会导致同样多的并发问题。

如果不做出实质性的性能牺牲,几乎不可能做出这些保证。因此,留给用户考虑他们将如何管理并发问题。这符合 Python 的哲学之一 "we're all consenting adults"。也就是说,如果您正在编写 class 我认为您应该知道在访问属性之前需要获取锁的属性是合理的。或者,如果您真的那么担心,请编写一个 wrapper/proxy class 来控制对基础对象的所有访问。

对于您的示例,目标对象可能会通过多种方式意外逃脱。如果程序员没有对他们 writing/maintaining 的代码给予足够的关注,那么这个 HiddenLock 可能会提供错误的安全感。例如:

with the_lock as obj:
    pass
obj.func() # erroneous

with the_lock as obj:
    return obj.func() # possibly erroneous
# What if the return value of `func' contains a self reference?

with the_lock as obj:
    obj_copy = obj[:]

obj_copy[0] = 2 # erroneous?

最后一个特别有害。此代码是否线程安全不取决于 with 块内的代码,甚至不取决于块之后的代码。相反,obj 的 class 的实现将意味着此代码是否是线程安全的。例如,如果 objlist 那么这是安全的,因为 obj[:] 创建了一个副本。但是,如果 objnumpy.ndarray,则 obj[:] 会创建一个视图,因此该操作是不安全的。

实际上,如果 obj 的内容是可变的,那么这可能是不安全的(例如 obj_copy[0].mutate())。

创建具有 list 并使用 threading.Lock 实现所需 class 方法的 shared_list 怎么样:

import threading

class SharedList(object):
    def __init__(self, iterable=None):
        if iterable is not None:
            self.list = list(iterable)
        else:
            self.list = list()
        self.lock = threading.Lock()
        self.index = None

    def append(self, x):
        with self.lock:
            self.list.append(x)

    def __iter__(self):
        shared_iterator = SharedList()
        shared_iterator.list = self.list
        shared_iterator.lock = self.lock
        shared_iterator.index = 0
        return shared_iterator

    def next(self):
        with self.lock:
            if self.index < len(self.list):
                result = self.list[self.index]
                self.index += 1
            else:
                raise StopIteration
            return result

    # Override other methods

if __name__ == '__main__':
    shared_list = SharedList()
    for x in range(1, 4):
        shared_list.append(x)
    for entry in shared_list:
        print entry

输出

1
2
3

正如 Georg Shölly 在评论中指出的那样,这将需要大量工作来实现每个方法。但是,如果您只需要一个可以追加然后迭代的列表,则此示例提供了起点。

那你就可以写

the_list = SharedList()
the_list.append("New Element")