在前一篇文章 python線程建立和傳參 中咱們介紹了關於python線程的一些簡單函數使用和線程的參數傳遞,使用多線程能夠同時執行多個任務,提升開發效率,可是在實際開發中每每咱們會碰到線程同步問題,假若有這樣一個場景:對全局變量累加1000000次,爲了提升效率,咱們可使用多線程完成,示例代碼以下:python
1git 2程序員 3github 4微信 5多線程 6ide 7函數 8ui 9spa 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# !usr/bin/env python # -*- coding:utf-8 _*- """ @Author:何以解憂 @Blog(我的博客地址): shuopython.com @WeChat Official Account(微信公衆號):猿說python @Github:www.github.com
@File:python_thread_lock.py @Time:2019/10/17 21:22
@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 2 3 4 |
第1次計算結果:1262996 第2次計算結果:1661455 第3次計算結果:1300211 第4次計算結果:1563699 |
what ? 這是什麼操做??看着代碼好像也沒問題,兩個線程,各自累加1000000次,不該該輸出是2000000次嗎?並且調用了4次main函數,每次輸出的結果還不一樣!!
分析下上面的代碼:兩個線程共享全局變量並執行for循環1000000,每次自動加1,咱們都知道兩個線程都是同時在運行,也就是說兩個線程同時在執行 g_num = g_num + 1 操做, 通過咱們冷靜分析一波,貌似結果仍是應該等於2000000,對不對?
首先,咱們將上面全局變量自動加 1 的代碼分爲兩步:
1 2 |
第一步: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,偏差就由此產生,每每循環次數越多,產生的偏差就越大。
爲了不上述問題,咱們能夠利用線程互斥鎖解決這個問題。那麼互斥鎖究竟是個什麼原理呢?互斥鎖就比如排隊上廁所,一個坑位只能蹲一我的,只有佔用坑位的人完事了,另一我的才能上!
導入線程模塊,經過 threading.Lock() 建立互斥鎖.
1 2 3 4 5 |
# 導入線程threading模塊 import threading
# 建立互斥鎖 mutex = threading.Lock() |
acquire() — 鎖定資源,此時資源是鎖定狀態,其餘線程沒法修改鎖定的資源,直到等待鎖定的資源釋放以後才能操做;
release() — 釋放資源,也稱爲解鎖操做,對鎖定的資源解鎖,解鎖以後其餘線程能夠對資源正常操做;
以上面的代碼爲列子:想獲得正確的結果,能夠直接利用互斥鎖在全局變量 加1 以前 鎖定資源,而後在計算完成以後釋放資源,這樣就是一個完整的計算過程,至於應該是哪一個線程先執行,無所謂,先到先得,憑本事說話….演示代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# !usr/bin/env python # -*- coding:utf-8 _*- """ @Author:何以解憂 @Blog(我的博客地址): shuopython.com @WeChat Official Account(微信公衆號):猿說python @Github:www.github.com
@File:python_thread_lock.py @Time:2019/10/18 21:22
@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 2 3 4 |
第1次計算結果:2000000 第2次計算結果:2000000 第3次計算結果:2000000 第4次計算結果:2000000 |
因而可知,全局變量計算加上互斥鎖以後,不論執行多少次,計算結果都相同。注意:互斥鎖一旦鎖定以後要記得解鎖,不然資源會一直處於鎖定狀態;
1.單個互斥鎖的死鎖:acquire()/release() 是成對出現的,互斥鎖對資源鎖定以後就必定要解鎖,不然資源會一直處於鎖定狀態,其餘線程沒法修改;就比如上面的代碼,任何一個線程沒有釋放資源release(),程序就會一直處於阻塞狀態(在等待資源被釋放),不信你能夠試一試~
2.多個互斥鎖的死鎖:在同時操做多個互斥鎖的時候必定要格外當心,由於一不當心就容易進入死循環,假若有這樣一個場景:boss讓程序員一實現功能一的開發,讓程序員二實現功能二的開發,功能開發完成以後一塊兒整合代碼!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# 導入線程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 2 |
我是程序員1,module1開發正式開始,誰也別動個人代碼 我是程序員2,module2開發正式開始,誰也別動個人代碼 |
分析下上面代碼:程序員1在等程序員2通知,程序員2在等程序員1通知,兩個線程都陷入阻塞中,由於兩個線程都在等待對方解鎖,這就是死鎖!因此在開發中對於死鎖的問題仍是須要多多注意!
1.線程與線程之間共享全局變量須要設置互斥鎖;
2.注意在互斥鎖操做中 acquire()/release() 成對出現,避免形成死鎖;
轉載請註明:猿說Python » Python線程互斥鎖Lock