本文來自於千鋒教育在阿里雲開發者社區學習中心上線課程《Python入門2020最新大課》,主講人姜偉。
線程鎖的使用
同步
當多個線程幾乎同時修改某一個共享數據的時候,需要進行同步控制。同步就是協同步調,按預定的先後次序進行運行。線程同步能夠保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。
互斥鎖
互斥鎖為資源引入一個狀態:鎖定/非鎖定
某個線程要更改共享數據時,先將其鎖定,此時資源的狀態為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。
threading模塊中定義了Lock類,可以方便的處理鎖定:
# 創建鎖
mutex = threading.Lock()
# 鎖定
mutex.acquire()
# 釋放
mutex.release()
注意:
- 如果這個鎖之前是沒有上鎖的,那麼acquire不會堵塞
- 如果在調用acquire對這個鎖上鎖之前 它已經被 其他線程上了鎖,那麼此時acquire會堵塞,直到這個鎖被解鎖為止。
- 和文件操作一樣,Lock也可以使用with語句快速的實現打開和關閉操作。
使用互斥鎖解決賣票問題
import threading
import time
ticket = 20
# 創建一把鎖
lock = threading.Lock()
def sell_ticket():
global ticket
while True:
print('呵呵呵')
print('哈哈哈')
print('ddd')
print('ppp')
print('sss')
print('ttt')
print('xxx')
lock.acquire() # 加同步鎖
if ticket > 0:
time.sleep(1)
ticket -= 1
lock.release()
print('{}賣出一張票,還剩{}張'.format(threading.current_thread().name, ticket))
else:
lock.release()
print('票賣完了')
break
t1 = threading.Thread(target=sell_ticket, name='線程1')
t2 = threading.Thread(target=sell_ticket, name='線程2')
t1.start()
t2.start()
上鎖過程:
當一個線程調用鎖的acquire()方法獲得鎖時,鎖就進入“locked”狀態。
每次只有一個線程可以獲得鎖。如果此時另一個線程試圖獲得這個鎖,該線程就會變為“blocked”狀態,稱為“阻塞”,直到擁有鎖的線程調用鎖的release()方法釋放鎖之後,鎖進入“unlocked”狀態。
線程調度程序從處於同步阻塞狀態的線程中選擇一個來獲得鎖,並使得該線程進入運行(running)狀態。
總結
鎖的好處:
確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行
鎖的壞處:
- 阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地下降了。
- 由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖。