[英]Python Locking Implementation (with threading module)
這可能是一個基本問題,但我不熟悉Python中的線程編程,並不完全確定正確的做法是什么。
我應該創建一個單獨的鎖對象(全局或被傳遞)並在我需要鎖定的任何地方使用它? 或者,我應該在每個將要使用它們的類中創建多個鎖實例。 拿這兩個基本的代碼樣本,哪個方向最好去? 主要區別在於第二個類A和B都使用單個鎖實例,而第一個使用多個實例。
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()
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.