您好,登錄后才能下訂單哦!
在前一篇文章?python線程創建和傳參?中我們介紹了關于python線程的一些簡單函數使用和線程的參數傳遞,使用多線程可以同時執行多個任務,提高開發效率,但是在實際開發中往往我們會碰到線程同步問題,假如有這樣一個場景:對全局變量累加1000000次,為了提高效率,我們可以使用多線程完成,示例代碼如下:
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 | # !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()?成對出現,避免造成死鎖;
?
?
1.python線程創建和傳參
2.python函數-缺省參數
3.python局部變量和全局變量
?
轉載請注明:猿說Python???Python線程互斥鎖Lock?
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。