简体   繁体   English

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

[英]Python Locking Implementation (with threading module)

This is probably a rudimentary question, but I'm new to threaded programming in Python and am not entirely sure what the correct practice is. 这可能是一个基本问题,但我不熟悉Python中的线程编程,并不完全确定正确的做法是什么。

Should I be creating a single lock object (either globally or being passed around) and using that everywhere that I need to do locking? 我应该创建一个单独的锁对象(全局或被传递)并在我需要锁定的任何地方使用它? Or, should I be creating multiple lock instances in each of the classes where I will be employing them. 或者,我应该在每个将要使用它们的类中创建多个锁实例。 Take these 2 rudimentary code samples, which direction is best to go? 拿这两个基本的代码样本,哪个方向最好去? The main difference being that a single lock instance is used in both class A and B in the second, while multiple instances are used in the first. 主要区别在于第二个类A和B都使用单个锁实例,而第一个使用多个实例。

Sample 1 样品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()

Sample 2 样本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()

In the general case, a single global lock is less efficient (more contention) but safer (no risk of deadlock) as long as it's a RLock (reentrant) rather than a plain Lock . 在一般情况下,单个全局锁效率较低(更多争用)但更安全(没有死锁风险),只要它是RLock (可重入)而不是普通Lock

The potential problems come when a thread that's executing while holding a lock tries to acquire another (or the same) lock, for example by calling another method that contains the acquire call. 当持有锁执行的线程试图获取另一个(或相同的)锁时,例如通过调用包含acquire调用的另一个方法,可能会出现问题。 If a thread that's already holding a lock tries to acquire it again, it will block forever if the lock's a plain Lock , but proceed smoothly if it's a slightly more complex RLock -- that's why the latter is called reentrant , because the thread holding it can "enter" (acquire the lock) again. 如果一个已经持有锁的线程试图再次获取它,它将永远阻塞,如果锁是一个普通的Lock ,但如果它是一个稍微复杂的RLock则顺利进行 - 这就是后者被称为可重入的原因,因为持有它的线程可以再次“进入”(获得锁定)。 Essentially, a RLock keeps track of which thread holds it, and how many time the thread has acquired the lock, while the simpler Lock does not keep such information around. 本质上,RLock会跟踪哪个线程持有它,以及线程获取锁定的时间,而更简单的Lock不会保留此类信息。

With multiple locks, the deadlock problem comes when one thread tries to acquire lock A then lock B, while another tries to acquire first lock B, then lock A. If that occurs, then sooner or later you'll be in a situation where the first lock holds A, the second one holds B, and each tries to acquire the lock that the other one is holding -- so both block forever. 使用多个锁定时,当一个线程尝试获取锁定A然后锁定B时会出现死锁问题,而另一个线程尝试获取第一个锁定B,然后锁定A.如果发生这种情况,那么迟早您将处于以下情况:第一个锁持有A,第二个持有B,每个都试图获取另一个持有的锁 - 所以两者都永远阻塞。

One way to prevent multiple-lock deadlocks is to make sure that locks are always acquired in the same order, whatever thread is doing the acquiring. 防止多锁死锁的一种方法是确保始终以相同的顺序获取锁,无论线程正在进行获取。 However, when each instance has its own lock, that's exceedingly difficult to organize with any clarity and simplicity. 但是,当每个实例都有自己的锁时,以任何清晰和简单的方式组织起来非常困难。

If you use a separate lock object in each class then you run a risk of deadlocking, eg if one operation claims the lock for A and then claims the lock for B while a different operation claims B and then A. 如果在每个类中使用单独的锁定对象,则存在死锁风险,例如,如果一个操作声明锁定A,然后声明锁定B,而另一个操作声明B然后A.

If you use a single lock then you're forcing code to single thread when different operations could be run in parallel. 如果您使用单个锁,那么当可以并行运行不同的操作时,您将强制代码转换为单个线程。 That isn't always as serious in Python (which has a global lock in any case) as in other languages, but say you were to hold a global lock while writing to a file Python would release the GIL but you'd have blocked everything else. 这在Python中并不总是那么严重(在任何情况下都有全局锁定),就像在其他语言中一样,但是说你在写入文件时要保持全局锁定Python会释放GIL但是你已经阻止了一切其他。

So it's a tradeoff. 所以这是一个权衡。 I'd say go for little locks as that way you maximise the chance for parallel execution, but take care never to claim more than one lock at a time, and try not to hold onto a lock for any longer than you absolutely have to. 我会说你可以选择小锁,这样你就可以最大限度地提高并行执行的几率,但是要注意不要一次声称有多个锁,并且尽量不要长时间保持锁定。

So far as your specific examples go, the first one is just plain broken. 就你的具体例子而言,第一个例子就是简单的。 If you lock operations on theList then you must use the same lock every time or you aren't locking anything. 如果您锁定了theList操作,那么每次都必须使用相同的锁,否则您不会锁定任何内容。 That may not matter here as list.append and list.remove are effectively atomic anyway, but if you do need to lock access to the list you need to be sure to use the same lock every time. 这可能无关紧要,因为list.append和list.remove无论如何都是有效的原子,但是如果你确实需要锁定对列表的访问,你需要确保每次都使用相同的锁。 The best way to do that is to hold the list and a lock as attributes of a class and force all access to the list to go through methods of the containing class. 执行此操作的最佳方法是将列表和锁定作为类的属性,并强制对列表的所有访问权限通过包含类的方法。 Then pass the container class around not the list or the lock. 然后传递容器类而不是列表或锁。

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

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