簡體   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