目錄
- 一.前言
- 二.Python 執行緒共享全域變數
- 三.Python 執行緒互斥鎖
- 1.創建互斥鎖
- 2.鎖定資源/解鎖資源
- 四.Python 執行緒死鎖
- 五.重點總結
- 六.猜你喜歡
一.前言
在前一篇文章 Python 執行緒創建和傳參 中我們介紹了關于 Python 執行緒的一些簡單函式使用和執行緒的引數傳遞,使用多執行緒可以同時執行多個任務,提高開發效率,但是在實際開發中往往我們會碰到執行緒同步問題,假如有這樣一個場景:對全域變數累加 1000000 次,為了提高效率,我們可以使用多執行緒完成,示例代碼如下:
# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:猿說編程
@Blog(個人博客地址): www.codersrc.com
@File:Python 執行緒互斥鎖 Lock.py
@Time:2021/04/22 08:00
@Motto:不積跬步無以至千里,不積小流無以成江海,程式人生的精彩需要堅持不懈地積累!
"""
# 匯入執行緒threading模塊
import threading
# 宣告全域變數
g_num = 0
def my_thread1():
# 宣告全域變數
global g_num
# 回圈 1000000 次,每次累計加 1
for i in range(0,1000000):
g_num = g_num + 1
def my_thread2():
# 宣告全域變數
global g_num
# 回圈 1000000 次,每次累計加 1
for i in range(0,1000000):
g_num = g_num + 1
def main(i):
# 宣告全域變數
global g_num
# 初始化全域變數,初始值為 0
g_num = 0
# 創建兩個執行緒,對全域變數進行累計加 1
t1 = threading.Thread(target=my_thread1)
t2 = threading.Thread(target=my_thread2)
# 啟動執行緒
t1.start()
t2.start()
# 阻塞函式,等待執行緒結束
t1.join()
t2.join()
# 獲取全域變數的值
print("第%d次計算結果:%d "% (i,g_num))
if __name__ == "__main__":
# 回圈4次,呼叫main函式,計算全域變數的值
for i in range(1,5):
main(i)
'''
輸出結果:
第1次計算結果:1262996
第2次計算結果:1661455
第3次計算結果:1300211
第4次計算結果:1563699
'''
what ? 這是什么操作??看著代碼好像也沒問題,兩個執行緒,各自累加 1000000 次,不應該輸出是 2000000 次嗎?而且呼叫了 4 次 main 函式,每次輸出的結果還不同!!

二.Python 執行緒共享全域變數
分析下上面的代碼:兩個執行緒共享全域變數并執行 for 回圈 1000000 ,每次自動加 1 ,我們都知道兩個執行緒都是同時在運行,也就是說兩個執行緒同時在執行 g_num = g_num + 1 操作, 經過我們冷靜分析一波,貌似結果還是應該等于 2000000 ,對不對?

首先,我們將上面全域變數自動加 1 的代碼分為兩步:
第一步:g_num + 1
第二步:將 g_num + 1 的結果賦值給 g_num
由此可見,執行一個完整的自動加 1 程序需要兩步,然而執行緒卻是在同時運行,誰也不能保證執行緒 1 的第一步和第二步執行完成之后才執行執行緒 2 的第一步和第二步,執行的程序充滿隨機性,這就是導致每次計算結果不同的原因所在!
舉個簡單的例子:
假如當前 g_num 值是 100,當執行緒 1 執行第一步時,cpu 通過計算獲得結果 101,并準備把計算的結果 101 賦值給 g_num,然后再傳值的程序中,執行緒 2 突然開始執行了并且執行了第一步,此時 g_num 的值仍未 100,101 還在傳遞的程序中,還沒成功賦值,執行緒 2 獲得計算結果 101 ,并準備傳遞給 g_num ,經過一來一去這么一折騰,分明做了兩次加 1 操作,g_num 結果卻是 101 ,誤差就由此產生,往往回圈次數越多,產生的誤差就越大,?

三.Python 執行緒互斥鎖
為了避免上述問題,我們可以利用執行緒互斥鎖解決這個問題,那么互斥鎖到底是個什么原理呢?互斥鎖就好比排隊上廁所,一個坑位只能蹲一個人,只有占用坑位的人完事了,另外一個人才能上!

1.創建互斥鎖
匯入執行緒模塊,通過 threading.Lock 創建互斥鎖.
# 匯入執行緒threading模塊
import threading
# 創建互斥鎖
mutex = threading.Lock()
2.鎖定資源/解鎖資源
- **acquire **— 鎖定資源,此時資源是鎖定狀態,其他執行緒無法修改鎖定的資源,直到等待鎖定的資源釋放之后才能操作;
- release — 釋放資源,也稱為解鎖操作,對鎖定的資源解鎖,解鎖之后其他執行緒可以對資源正常操作;
以上面的代碼為列子:想得到正確的結果,可以直接利用互斥鎖在全域變數 加 1 之前 鎖定資源,然后在計算完成之后釋放資源,這樣就是一個完整的計算程序,至于應該是哪個執行緒先執行,無所謂,先到先得,憑本事說話….演示代碼如下:
# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:猿說編程
@Blog(個人博客地址): www.codersrc.com
@File:Python 執行緒互斥鎖 Lock.py
@Time:2021/04/22 08:00
@Motto:不積跬步無以至千里,不積小流無以成江海,程式人生的精彩需要堅持不懈地積累!
"""
# 匯入執行緒threading模塊
import threading
# 宣告全域變數
g_num = 0
# 創建互斥鎖
mutex = threading.Lock()
def my_thread1():
# 宣告全域變數
global g_num
# 回圈 1000000 次,每次累計加 1
for i in range(0,1000000):
# 鎖定資源
mutex.acquire()
g_num = g_num + 1
# 解鎖資源
mutex.release()
def my_thread2():
# 宣告全域變數
global g_num
# 回圈 1000000 次,每次累計加 1
for i in range(0,1000000):
# 鎖定資源
mutex.acquire()
g_num = g_num + 1
# 解鎖資源
mutex.release()
def main(i):
# 宣告全域變數
global g_num
# 初始化全域變數,初始值為 0
g_num = 0
# 創建兩個執行緒,對全域變數進行累計加 1
t1 = threading.Thread(target=my_thread1)
t2 = threading.Thread(target=my_thread2)
# 啟動執行緒
t1.start()
t2.start()
# 阻塞函式,等待執行緒結束
t1.join()
t2.join()
# 獲取全域變數的值
print("第%d次計算結果:%d "% (i,g_num))
if __name__ == "__main__":
# 回圈4次,呼叫main函式,計算全域變數的值
for i in range(1,5):
main(i)
'''
輸出結果:
第1次計算結果:2000000
第2次計算結果:2000000
第3次計算結果:2000000
第4次計算結果:2000000
'''
由此可見,全域變數計算加上互斥鎖之后,不論執行多少次,計算結果都相同,注意:互斥鎖一旦鎖定之后要記得解鎖,否則資源會一直處于鎖定狀態;
四.Python 執行緒死鎖
1.單個互斥鎖的死鎖:acquire / release 是成對出現的,互斥鎖對資源鎖定之后就一定要解鎖,否則資源會一直處于鎖定狀態,其他執行緒無法修改;就好比上面的代碼,任何一個執行緒沒有釋放資源 release,程式就會一直處于阻塞狀態(在等待資源被釋放),不信你可以試一試~
2.多個互斥鎖的死鎖:在同時操作多個互斥鎖的時候一定要格外小心,因為一不小心就容易進入死回圈,假如有這樣一個場景:boss 讓程式員一實作功能一的開發,讓程式員二實作功能二的開發,功能開發完成之后一起整合代碼!
# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:猿說編程
@Blog(個人博客地址): www.codersrc.com
@File:Python 執行緒互斥鎖 Lock.py
@Time:2021/04/22 08:00
@Motto:不積跬步無以至千里,不積小流無以成江海,程式人生的精彩需要堅持不懈地積累!
"""
# 匯入執行緒threading模塊
import threading
# 匯入執行緒time模塊
import time
# 創建互斥鎖
mutex_one = threading.Lock()
mutex_two = threading.Lock()
def programmer_thread1():
mutex_one.acquire()
print("我是程式員1,module1開發正式開始,誰也別動我的代碼")
time.sleep(2)
# 此時會堵塞,因為這個mutex_two已經被執行緒programmer_thread2搶先上鎖了,等待解鎖
mutex_two.acquire()
print("等待程式員2通知我合并代碼")
mutex_two.release()
mutex_one.release()
def programmer_thread2():
mutex_two.acquire()
print("我是程式員2,module2開發正式開始,誰也別動我的代碼")
time.sleep(2)
# 此時會堵塞,因為這個mutex_one已經被執行緒programmer_thread1搶先上鎖了,等待解鎖
mutex_one.acquire()
print("等待程式員1通知我合并代碼")
mutex_one.release()
mutex_two.release()
def main():
t1 = threading.Thread(target=programmer_thread1)
t2 = threading.Thread(target=programmer_thread2)
# 啟動執行緒
t1.start()
t2.start()
# 阻塞函式,等待執行緒結束
t1.join()
t2.join()
# 整合代碼結束
print("整合代碼結束 ")
if __name__ == "__main__":
main()
'''
輸出結果:
我是程式員1,module1開發正式開始,誰也別動我的代碼
我是程式員2,module2開發正式開始,誰也別動我的代碼
'''
分析下上面代碼:程式員 1 在等程式員 2 通知,程式員 2 在等程式員 1 通知,兩個執行緒都陷入阻塞中,因為兩個執行緒都在等待對方解鎖,這就是死鎖!所以在開發中對于死鎖的問題還是需要多多注意!
五.重點總結
- 1.執行緒與執行緒之間共享全域變數需要設定互斥鎖;
- 2.注意在互斥鎖操作中 **acquire / release **成對出現,避免造成死鎖;
六.猜你喜歡
- Python for 回圈
- Python 字串
- Python 串列 list
- Python 元組 tuple
- Python 字典 dict
- Python 條件推導式
- Python 串列推導式
- Python 字典推導式
- Python 函式宣告和呼叫
- Python 不定長引數 *argc/**kargcs
- Python 匿名函式 lambda
- Python return 邏輯判斷運算式
- Python 字串/串列/元組/字典之間的相互轉換
- Python 區域變數和全域變數
- Python type 函式和 isinstance 函式區別
- Python is 和 == 區別
- Python 可變資料型別和不可變資料型別
- Python 淺拷貝和深拷貝
未經允許不得轉載:猿說編程 ? Python 執行緒互斥鎖 Lock
本文由博客 - 猿說編程 猿說編程 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/288314.html
標籤:其他
