繁体   English   中英

Python锁定实现(带有线程模块)

[英]Python Locking Implementation (with threading module)

这可能是一个基本问题,但我不熟悉Python中的线程编程,并不完全确定正确的做法是什么。

我应该创建一个单独的锁对象(全局或被传递)并在我需要锁定的任何地方使用它? 或者,我应该在每个将要使用它们的类中创建多个锁实例。 拿这两个基本的代码样本,哪个方向最好去? 主要区别在于第二个类A和B都使用单个锁实例,而第一个使用多个实例。

样品1

class A():
    def __init__(self, theList):
        self.theList = theList
        self.lock = threading.Lock()

    def poll(self):
        while True:
            # do some stuff that eventually needs to work with theList
            self.lock.acquire()
            try:
                self.theList.append(something)
            finally:
                self.lock.release()





class B(threading.Thread):
    def __init__(self,theList):
        self.theList = theList
        self.lock = threading.Lock()
        self.start()


    def run(self):
        while True:
            # do some stuff that eventually needs to work with theList
            self.lock.acquire()
            try:
                self.theList.remove(something)
            finally:
                self.lock.release()



if __name__ == "__main__":
    aList = []
    for x in range(10):
        B(aList)

    A(aList).poll()

样本2

class A():
    def __init__(self, theList,lock):
        self.theList = theList
        self.lock = lock

    def poll(self):
        while True:
            # do some stuff that eventually needs to work with theList
            self.lock.acquire()
            try:
                self.theList.append(something)
            finally:
                self.lock.release()



class B(threading.Thread):
    def __init__(self,theList,lock):
        self.theList = theList
        self.lock = lock
        self.start()


    def run(self):
        while True:
            # do some stuff that eventually needs to work with theList
            self.lock.acquire()
            try:
                self.theList.remove(something)
            finally:
                self.lock.release()



if __name__ == "__main__":
    lock = threading.Lock()
    aList = []
    for x in range(10):
        B(aList,lock)

    A(aList,lock).poll()

在一般情况下,单个全局锁效率较低(更多争用)但更安全(没有死锁风险),只要它是RLock (可重入)而不是普通Lock

当持有锁执行的线程试图获取另一个(或相同的)锁时,例如通过调用包含acquire调用的另一个方法,可能会出现问题。 如果一个已经持有锁的线程试图再次获取它,它将永远阻塞,如果锁是一个普通的Lock ,但如果它是一个稍微复杂的RLock则顺利进行 - 这就是后者被称为可重入的原因,因为持有它的线程可以再次“进入”(获得锁定)。 本质上,RLock会跟踪哪个线程持有它,以及线程获取锁定的时间,而更简单的Lock不会保留此类信息。

使用多个锁定时,当一个线程尝试获取锁定A然后锁定B时会出现死锁问题,而另一个线程尝试获取第一个锁定B,然后锁定A.如果发生这种情况,那么迟早您将处于以下情况:第一个锁持有A,第二个持有B,每个都试图获取另一个持有的锁 - 所以两者都永远阻塞。

防止多锁死锁的一种方法是确保始终以相同的顺序获取锁,无论线程正在进行获取。 但是,当每个实例都有自己的锁时,以任何清晰和简单的方式组织起来非常困难。

如果在每个类中使用单独的锁定对象,则存在死锁风险,例如,如果一个操作声明锁定A,然后声明锁定B,而另一个操作声明B然后A.

如果您使用单个锁,那么当可以并行运行不同的操作时,您将强制代码转换为单个线程。 这在Python中并不总是那么严重(在任何情况下都有全局锁定),就像在其他语言中一样,但是说你在写入文件时要保持全局锁定Python会释放GIL但是你已经阻止了一切其他。

所以这是一个权衡。 我会说你可以选择小锁,这样你就可以最大限度地提高并行执行的几率,但是要注意不要一次声称有多个锁,并且尽量不要长时间保持锁定。

就你的具体例子而言,第一个例子就是简单的。 如果您锁定了theList操作,那么每次都必须使用相同的锁,否则您不会锁定任何内容。 这可能无关紧要,因为list.append和list.remove无论如何都是有效的原子,但是如果你确实需要锁定对列表的访问,你需要确保每次都使用相同的锁。 执行此操作的最佳方法是将列表和锁定作为类的属性,并强制对列表的所有访问权限通过包含类的方法。 然后传递容器类而不是列表或锁。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM