您好,登錄后才能下訂單哦!
這篇文章主要講解了“Python線程鎖Lock的使用介紹”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Python線程鎖Lock的使用介紹”吧!
我們知道Python的線程是封裝了底層操作系統的線程,在Linux系統中是Pthread(全稱為POSIX Thread),在Windows中是Windows Thread。因此Python的線程是完全受操作系統的管理的。但是在計算密集型的任務中多線程反而比單線程更慢。
這是為什么呢?
在CPython 解釋器中執行線程時,每一個線程開始執行時,都會鎖住 GIL,以阻止別的線程執行。同樣的,每一個線程執行完一段后,會釋放 GIL,以允許別的線程開始利用資源。畢竟,如果Python線程在開始的時候鎖住GIL而不去釋放GIL,那別的線程就沒有運行的機會了。
為什么要這么處理呢?
我們先來介紹下競爭條件(race condition)這個概念。競爭條件是指兩個或者多個線程同時競爭訪問的某個資源(該資源本身不能被同時訪問),有可能因為時間上存在先后原因而出現問題,這種情況叫做競爭條件(Race Condition)。(Python中進程是有獨立的資源分配,線程是共用資源分配)
回到CPython上,CPython是使用引用計數器來管理內存的,所有創建的對象,都會有一個引用計數來記錄有多少個指針指向它。如下所示:
a_val = [] def ReferCount(): print(sys.getrefcount(a_val)) # 2 b = a_val c = a_val print(sys.getrefcount(a_val)) # 4
當引用計數為0時,CPython解釋器會自動釋放內存。這樣一來,如果有兩個Python線程同時引用了一個變量,就會造成引用計數的競爭條件(race condition)。因此引用計數變量需要在兩個線程同時增加或減少時從競爭條件中得到保護。如果發生了這種情況,可能會導致泄露的內存永遠不會被釋放,更嚴重的是當一個對象的引用仍然存在的情況下錯誤地釋放內存,導致Python程序崩潰或帶來各種詭異的問題。
以下是官方給的解釋:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
如何繞過GIL的限制?
目前像NumPy的矩陣運算這些高性能的應用場景是通過C/C++來實現Python庫,可以避免CPython解釋器的GIL限制。另一方面,當涉及到對性能非常嚴格的應用場景時,可以把關鍵代碼用C/C++來實現,然后通過Python調用這些程序,以此擺脫GIL的限制。
有了GIL機制是否還需要考慮競爭條件嗎?
GIL的設計是為了方便CPython解釋器層面的編寫者,而不是Python應用層面的程序員。作為Python的使用者,我們還是需要用Lock等工具來鎖住資源,來確保線程安全。
接下來我們就介紹下如何使用Lock機制。
Lock的使用主要有以下幾個方法:
mutex = threading.Lock() # 創建鎖
mutex.acquire([timeout]) # 鎖定
mutex.release() # 釋放
例如以下例程:
g_count = 0 def func(str_val): global g_count for i in range(1000000): g_count += 1 print(str_val+':g_count=%s' % g_count) def test_func_lock(): t1 = threading.Thread(target=func,args=['func1']) t2 = threading.Thread(target=func,args=['func2']) t1.start() t2.start() t1.join() t2.join()
最終返回的結果有這些情況:
func2:g_count=1509057 func1:g_count=1489782
func1:g_count=1305421 func2:g_count=1684556
func2:g_count=1545063 func1:g_count=1547995
……
理論上最后的結果應該是2000000,由于線程被調用執行的順序并不確定,同時存在執行遞增語句時切換線程,導致最后的結果并不是正確結果。
我們通過建立一個線程鎖來解決這個問題。如下所示:
g_count = 0 lock = threading.Lock() def func(str_val): global g_count for i in range(1000000): lock.acquire() g_count += 1 lock.release() print(str_val+':g_count=%s' % g_count)
執行結果為:func2:g_count=1988364 func1:g_count=2000000
比如線程t1使用lock.acquire()獲得了這個鎖,那么線程t2就無法再獲得該鎖了,只會阻塞在 lock.acquire()處,直到鎖被線程t1釋放,即執行lock.release()。如此一來就不會出現執行了一半就暫停去執行別的線程的情況,最后結果是正確的2000000。
最后給大家推薦一個更精簡的鎖的用法:
def threading_lock_test(): # 創建鎖 lock = threading.Lock() # 使用鎖的老方法 lock.acquire() try: print('Critical section 1') print('Critical section 2') finally: lock.release() # 使用鎖的新方法 with lock: print('Critical section 1') print('Critical section 2')
感謝各位的閱讀,以上就是“Python線程鎖Lock的使用介紹”的內容了,經過本文的學習后,相信大家對Python線程鎖Lock的使用介紹這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。