Python | threading02 - 互斥锁解决多个线程之间随机调度,造成“线程不安全”的问题。
文章目錄
- 一、前言
- 二、線程不安全的現(xiàn)象
- 2.1、代碼
- 2.2、運(yùn)行
- 三、使用互斥鎖解決線程不安全
- 3.1、代碼
- 3.2、運(yùn)行
- 四、忘記釋放互斥鎖,造成死鎖
- 4.1、代碼
- 4.2、運(yùn)行
- 4.3、造成死鎖的一種常見案例
- 五、with語句拯救粗心的人類
- 5.1、with語句在互斥鎖上的使用
- 5.3、運(yùn)行的結(jié)果
一、前言
python多線程與單片機(jī)的RTOS在調(diào)度規(guī)則完全不一樣。python多線程的調(diào)度程序會在任何時候中斷線程(相當(dāng)于調(diào)度,所以python多線程的調(diào)度可以說是很難控制的),單片機(jī)的RTOS會有相應(yīng)的API來產(chǎn)生調(diào)度(調(diào)度是可控的)。
線程與協(xié)程之間的比較還有最后一點(diǎn)要說明:如果使用線程做過重要的編程,就知道寫出程序有多么困難,因?yàn)檎{(diào)度程序任何時候都可能中斷線程。必須記住保留鎖,去保護(hù)程序中的重要部分,防止多步操作在執(zhí)行的過程中被中斷,防止數(shù)據(jù)處于無效狀態(tài)。 —《流暢的Python》- Luciano Ramalho
所以,在我看來:python的協(xié)程與單片機(jī)的RTOS才是相似的。
—加粗樣式
多線程的優(yōu)勢在于并發(fā)性,即可以同時運(yùn)行多個任務(wù)。但是當(dāng)線程需要使用共享數(shù)據(jù)時,也可能會由于數(shù)據(jù)不同步產(chǎn)生“錯誤情況”,這是由系統(tǒng)的線程調(diào)度具有一定的隨機(jī)性造成的。
由于線程之間的任務(wù)執(zhí)行是CPU進(jìn)行隨機(jī)調(diào)度的,并且每個線程可能只執(zhí)行了n條指令之后就被切換到別的線程了。當(dāng)多個線程同時操作一個對象,如果沒有很好地保護(hù)該對象,會造成程序結(jié)果的不可預(yù)期,這被稱為“線程不安全”。為了保證數(shù)據(jù)安全,設(shè)計(jì)了線程鎖,即同一時刻只允許一個線程操作該數(shù)據(jù)。
B站有一個視頻講解線程安全問題,個人覺得不錯。
【2021最新版】Python 并發(fā)編程實(shí)戰(zhàn),用多線程、多進(jìn)程、多協(xié)程加速程序運(yùn)行
二、線程不安全的現(xiàn)象
2.1、代碼
代碼的目的是讓number累加到2000000(2百萬),線程counter_1將number累加到1000000,線程counter_2將number累加到2000000。
# python3.9 import threading import timenumber = 0 # lock = threading.Lock()def counter_1():"""子線程1,counter_1"""global number # 聲明number是全局變量,并不是函數(shù)的局部變量# global lock # lock.acquire()for i in range(1000000):number +=1print("子線程%s運(yùn)算結(jié)束后,number = %s" % (threading.current_thread().getName(),number))# lock.release()def counter_2():"""子線程2,counter_2"""global number # 聲明number是全局變量,并不是函數(shù)的局部變量# global lock# lock.acquire()for i in range(1000000):number +=1print("子線程%s運(yùn)算結(jié)束后,number = %s" % (threading.current_thread().getName(),number))# lock.release()def main():t1 = threading.Thread(target=counter_1,name="counter_1",daemon=True)t2 = threading.Thread(target=counter_2,name="counter_2",daemon=True)t1.start()t2.start()t1.join()t2.join()print("程序運(yùn)行結(jié)束,number =",number)if __name__ == "__main__":main()2.2、運(yùn)行
從運(yùn)行的四次結(jié)果看來,每一次的結(jié)果都不一樣,而且都沒有出現(xiàn)一次2000000。這個現(xiàn)象就是因?yàn)槎鄠€線程同時訪問一個對象所造成的線程不安全問題。
三、使用互斥鎖解決線程不安全
3.1、代碼
# python3.9 import threading import timenumber = 0 lock = threading.Lock() # 互斥鎖def counter_1():"""子線程1,counter_1"""global number # 聲明number是全局變量,并不是函數(shù)的局部變量global lock # 聲明lock是全局變量,并不是函數(shù)的局部變量 lock.acquire() # 獲取互斥鎖for i in range(1000000):number +=1print("子線程%s運(yùn)算結(jié)束后,number = %s" % (threading.current_thread().getName(),number))lock.release() # 釋放互斥鎖(千萬記得用完要釋放,否則會出現(xiàn)死鎖)def counter_2():"""子線程2,counter_2"""global number # 聲明number是全局變量,并不是函數(shù)的局部變量global lock # 聲明lock是全局變量,并不是函數(shù)的局部變量lock.acquire() # 獲取互斥鎖for i in range(1000000):number +=1print("子線程%s運(yùn)算結(jié)束后,number = %s" % (threading.current_thread().getName(),number))lock.release() # 釋放互斥鎖(千萬記得用完要釋放,否則會出現(xiàn)死鎖)def main():t1 = threading.Thread(target=counter_1,name="counter_1",daemon=True) #創(chuàng)建線程counter_1t2 = threading.Thread(target=counter_2,name="counter_2",daemon=True) #創(chuàng)建線程counter_2t1.start() # 啟動線程counter_1t2.start() # 啟動線程counter_2t1.join() # 阻塞主線程,等待線程counter_1運(yùn)行結(jié)束t2.join() # 阻塞主線程,等待線程counter_2運(yùn)行結(jié)束print("程序運(yùn)行結(jié)束,number =",number)if __name__ == "__main__":main()3.2、運(yùn)行
四次運(yùn)行的結(jié)果都符合預(yù)期的2000000,表示互斥鎖解決了線程不安全的問題。但是,獲取互斥鎖并使用完之后一定,一定,一定要釋放互斥鎖。否則,會出現(xiàn)死鎖的問題。(其他獲取互斥鎖的線程將一直阻塞在那里。)
四、忘記釋放互斥鎖,造成死鎖
4.1、代碼
# python3.9 import threading import timenumber = 0 lock = threading.Lock() # 互斥鎖def counter_1():"""子線程1,counter_1"""global number # 聲明number是全局變量,并不是函數(shù)的局部變量global lock # 聲明lock是全局變量,并不是函數(shù)的局部變量 lock.acquire() # 獲取互斥鎖for i in range(1000000):number +=1print("子線程%s運(yùn)算結(jié)束后,number = %s" % (threading.current_thread().getName(),number))# lock.release() # 釋放互斥鎖(千萬記得用完要釋放,否則會出現(xiàn)死鎖)def counter_2():"""子線程2,counter_2"""global number # 聲明number是全局變量,并不是函數(shù)的局部變量global lock # 聲明lock是全局變量,并不是函數(shù)的局部變量lock.acquire() # 獲取互斥鎖for i in range(1000000):number +=1print("子線程%s運(yùn)算結(jié)束后,number = %s" % (threading.current_thread().getName(),number))lock.release() # 釋放互斥鎖(千萬記得用完要釋放,否則會出現(xiàn)死鎖)def main():t1 = threading.Thread(target=counter_1,name="counter_1",daemon=True) #創(chuàng)建線程counter_1t2 = threading.Thread(target=counter_2,name="counter_2",daemon=True) #創(chuàng)建線程counter_2t1.start() # 啟動線程counter_1t2.start() # 啟動線程counter_2t1.join() # 阻塞主線程,等待線程counter_1運(yùn)行結(jié)束t2.join() # 阻塞主線程,等待線程counter_2運(yùn)行結(jié)束print("程序運(yùn)行結(jié)束,number =",number)if __name__ == "__main__":main()在這里我故意忘記將獲取的互斥鎖釋放回去了,會出現(xiàn)什么問題?
4.2、運(yùn)行
程序卡死在哪里??我在代碼上加入一句代碼看看程序是不是真的卡在線程counter_2里。
運(yùn)行代碼看看。
運(yùn)行的結(jié)果證明了線程counter_2被阻塞在lock.acquire( )代碼這里,并一直一直阻塞下去,造成整個python程序卡死。
所以,緊記要釋放互斥鎖。
4.3、造成死鎖的一種常見案例
為了簡化問題,我們設(shè)有兩個并發(fā)的線程( 線程A 和 線程B ),需要 資源1 和 資源2 .假設(shè) 線程A 需要 資源1 , 線程B 需要 資源2 .在這種情況下,兩個線程都使用各自的鎖,目前為止沒有沖突。現(xiàn)在假設(shè),在雙方釋放鎖之前, 線程A 需要 資源2 的鎖, 線程B 需要 資源1 的鎖,沒有資源線程不會繼續(xù)執(zhí)行。鑒于目前兩個資源的鎖都是被占用的,而且在對方的鎖釋放之前都處于等待且不釋放鎖的狀態(tài)。這是死鎖的典型情況。所以如上所說,使用鎖來解決同步問題是一個可行卻存在潛在問題的方案。 —摘自《python并行編程中文版》
五、with語句拯救粗心的人類
5.1、with語句在互斥鎖上的使用
# python3.9 import threading import timenumber = 0 lock = threading.Lock() # 互斥鎖def counter_1():"""子線程1,counter_1"""global number # 聲明number是全局變量,并不是函數(shù)的局部變量global lock # 聲明lock是全局變量,并不是函數(shù)的局部變量# 使用with語句管理互斥鎖 with lock:# 鎖的對象for i in range(1000000):number +=1print("子線程%s運(yùn)算結(jié)束后,number = %s" % (threading.current_thread().getName(),number))def counter_2():"""子線程2,counter_2"""global number # 聲明number是全局變量,并不是函數(shù)的局部變量global lock # 聲明lock是全局變量,并不是函數(shù)的局部變量# 使用with語句管理互斥鎖with lock:# 鎖的對象for i in range(1000000):number +=1print("子線程%s運(yùn)算結(jié)束后,number = %s" % (threading.current_thread().getName(),number))def main():t1 = threading.Thread(target=counter_1,name="counter_1",daemon=True) #創(chuàng)建線程counter_1t2 = threading.Thread(target=counter_2,name="counter_2",daemon=True) #創(chuàng)建線程counter_2t1.start() # 啟動線程counter_1t2.start() # 啟動線程counter_2t1.join() # 阻塞主線程,等待線程counter_1運(yùn)行結(jié)束t2.join() # 阻塞主線程,等待線程counter_2運(yùn)行結(jié)束print("程序運(yùn)行結(jié)束,number =",number)if __name__ == "__main__":main()5.3、運(yùn)行的結(jié)果
從運(yùn)行的結(jié)果看來并沒有出現(xiàn)線程不安全問題。
with語句真?zhèn)ゴ笥趾啙?#xff01;!!!
總結(jié)
以上是生活随笔為你收集整理的Python | threading02 - 互斥锁解决多个线程之间随机调度,造成“线程不安全”的问题。的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机硬软件故障实训报告,计算机维护维修
- 下一篇: python16进制转10进制_pyth