Python進階必備:線程模塊threading

      網友投稿 932 2025-04-08

      1. 戲說線程和進程


      對于新手來說,首先要理解線程的概念,以及為什么需要線程編程。什么是線程呢?網上一般是這樣定義的:線程(thread)是操作系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。哈哈,你聽懂了嗎?我覺得這樣的定義純粹是自說自話:新手看完了一臉懵逼,老鳥看完了不以為然。咱們還是用白話解釋一下吧:

      假定你經營著一家物業管理公司。最初,業務量很小,事事都需要你親力親為,給老張家修完暖氣管道,立馬再去老李家換電燈泡——這叫單線程,所有的工作都得順序執行。

      后來業務拓展了,你雇傭了幾個工人,這樣,你的物業公司就可以同時為多戶人家提供服務了——這叫多線程,你是主線程。

      工人們使用的工具,是物業管理公司提供的,大家共享——這叫多線程資源共享。

      工人們在工作中都需要管鉗,可是管鉗只有一把——這叫沖突。解決沖突的辦法有很多,比如排隊、等同事用完后的微信通知等——這叫線程同步。

      業務不忙的時候,你就在辦公室喝喝茶。下班時間一到,你群發微信,所有的工人不管手頭的工作是否完成,都立馬撂下工具,跟你走人。因此如果有必要,你得避免不要在工人正忙著的時候發下班的通知——這叫線程守護屬性設置和管理。

      再后來,你的公司規模擴大了,同時為很多生活社區服務,你在每個生活社區設置了分公司,分公司由分公司經理管理,運營機制和你的總公司幾乎一模一樣——這叫多進程,總公司叫主進程,分公司叫子進程。

      總公司以及各個分公司之間,工具都是獨立的,不能借用、混用——這叫進程間不能共享資源。各個分公司之間可以通過專線電話聯系——這叫管道。各個分公司之間還可以通過公司公告欄交換信息——這叫共享內存。

      分公司可以跟著總公司一起下班,也可以把當天的工作全部做完之后再下班——這叫守護進程設置。

      Python 提供了多個模塊來支持多線程編程,包括 thread、 threading 和 Queue 模塊等。程序是可以使用 thread 和 threading 模塊來創建與管理線程。 thread 模塊提供了基本的線程和鎖定支持;而 threading 模塊提供了更高級別、功能更全面的線程管理。我們在這里只討論 threading 模塊。

      2. 創建并使用線程

      使用 threading 模塊的 Thread 類,可以快速創建并啟動線程。當然,創建線程之前,你得先把交給線程去做的工作,寫成一個函數,我們管這個函數叫線程函數。

      threading.Thread 類有以下方法和屬性:

      我們設計一個任務:你(主線程)啟動3個子線程,名字分別是A、B、C。其中A線程啟動后,你要先觀察5秒鐘,再啟動其他線程。每個子線程的任務是每隔指定時間間隔就向你問好,并報上自己的名字,你呢,只管睡覺。20秒后,你醒了。你逐一檢查了各個子線程的工作狀態之后,結束運行。下面是實現代碼:

      import time import threading def hello(name, t): """線程函數""" for i in range(10): print('Hello, 我是小%s'%name) time.sleep(t) def demo(): A = threading.Thread(target=hello, args=('A',1), name='A') B = threading.Thread(target=hello, args=('B',2), name='B') C = threading.Thread(target=hello, args=('C',3), name='C') #C.setDaemon(True) # 設置子線程在主線程結束時是否無條件跟隨主線程一起退出 A.start() A.join(5) # 等待A線程結束,若5秒鐘后未結束,則代碼繼續 B.start() C.start() time.sleep(20) print('進程A%s'%('還在工作中' if A.isAlive() else '已經結束工作',)) print('進程B%s'%('還在工作中' if B.isAlive() else '已經結束工作',)) print('進程C%s'%('還在工作中' if C.isAlive() else '已經結束工作',)) print('下班了。。。') if __name__ == '__main__': demo()

      但是,運行這段代碼,你會發,當你喊下班的時候,小C并沒有立刻撂下手頭的活兒跟你走人,而是做完了工作之后才跟你走人——或者說,是你在等他做完工作后一起走人。這里容易產生誤會,以為主線程結束后,子線程還會工作到任務完成。這是錯誤的理解。真相是,主線程不忍心打斷正在忙碌的子線程(active),一旦該子線程休眠(inactive),不管任務是否結束,都會被主線程直接帶走。

      那么如何令子線程在主線程結束時無條件跟隨主線程一起走人呢?很簡單,在線程 start() 之前,使用 setDaemon(True) 設置該線程為守護線程就可以了。子線程的 daemon 屬性默認為 False。

      3. 線程同步

      3.1 線程鎖 Lock

      前幾天,我想在一個幾百人的微信群里統計喜歡吃蘋果的人數。有人說,大家從1開始報數吧,并敲了起始數字1,立馬有人敲了數字2,3。但是統計很快就進行不下去了,因為大家發現,有好幾個人敲4,有更多的人敲5。

      這就是典型的資源競爭沖突:統計用的計數器就是唯一的資源,很多人(子線程)都想取得寫計數器的資格。怎么辦呢?Lock(互斥鎖)就是一個很好的解決方案。Lock只能有一個線程獲取,獲取該鎖的線程才能執行,否則阻塞;執行完任務后,必須釋放鎖。

      請看演示代碼:

      # -*- encoding: utf8 -*- import time import threading lock = threading.Lock() # 創建互斥鎖 counter = 0 # 計數器 def hello(): """線程函數""" global counter if lock.acquire(): # 請求互斥鎖,如果被占用,則阻塞,直至獲取到鎖 time.sleep(0.2) # 假裝思考、敲鍵盤需要0.2秒鐘 counter += 1 print('我是第%d個'%counter) lock.release() # 千萬不要忘記釋放互斥鎖,否則后果很嚴重 def demo(): threads = list() for i in range(30): # 假設群里有30人,都喜歡吃蘋果 threads.append(threading.Thread(target=hello)) threads[-1].start() for t in threads: t.join() print('統計完畢,共有%d人'%counter) if __name__ == '__main__': demo()

      除了互斥鎖,線程鎖還有另一種形式,叫做遞歸鎖(RLock),又稱可重入鎖。已經獲得遞歸鎖的線程可以繼續多次獲得該鎖,而不會被阻塞,釋放的次數必須和獲取的次數相同才會真正釋放該鎖。欲了解詳情,同學們可以自行檢索資料。

      3.2 信號量 Semaphore

      上面的例子中,統計用的計數器是唯一的資源,因此使用了只能被一個線程獲取的互斥鎖。假如共享的資源有多個,多線程競爭時一般使用信號量(Semaphore)同步。信號量有一個初始值,表示當前可用的資源數,多線程執行過程中會通過 acquire() 和 release() 操作,動態的加減信號量。比如,有30個工人都需要電錘,但是電錘總共只有5把。使用信號量(Semaphore)解決競爭的代碼如下:

      # -*- encoding: utf8 -*- import time import threading S = threading.Semaphore(5) # 有5把電錘可供使用 def us_hammer(id): """線程函數""" S.acquire() # P操作,阻塞式請求電錘, time.sleep(0.2) print('%d號剛剛用完電錘'%id) S.release() # V操作,釋放資源(信號量加1) def demo(): threads = list() for i in range(30): # 有30名工人要求使用電錘 threads.append(threading.Thread(target=us_hammer, args=(i,))) threads[-1].start() for t in threads: t.join() print('所有線程工作結束') if __name__ == '__main__': demo()

      3.3 事件Event

      想象我們每天早上上班的場景:為了不遲到,總得提前幾分鐘(我一般都會提前30分鐘)到辦公室,打卡之后,一看表,還不到工作時間,大家就看看新聞、聊聊天啥的;工作時間一到,立馬開工。如果有人遲到了呢,自然就不能看新聞聊天了,得立即投入工作中。

      這個場景中,每個人代表一個線程,工作時間到,表示事件(Event)發生。事件發生前,線程會調用 wait() 方法阻塞自己(對應看新聞聊天),一旦事件發生,會喚醒所有調用 wait() 而進入阻塞狀態的線程。

      Python進階必備:線程模塊threading

      # -*- encoding: utf8 -*- import time import threading E = threading.Event() # 創建事件 def work(id): """線程函數""" print('<%d號員工>上班打卡'%id) if E.is_set(): # 已經到點了 print('<%d號員工>遲到了'%id) else: # 還不到點 print('<%d號員工>瀏覽新聞中...'%id) E.wait() # 等上班鈴聲 print('<%d號員工>開始工作了...'%id) time.sleep(10) # 工作10秒后下班 print('<%d號員工>下班了'%id) def demo(): E.clear() # 設置為“未到上班時間” threads = list() for i in range(3): # 3人提前來到公司打卡 threads.append(threading.Thread(target=work, args=(i,))) threads[-1].start() time.sleep(5) # 5秒鐘后上班時間到 E.set() time.sleep(5) # 5秒鐘后,大佬(9號)到 threads.append(threading.Thread(target=work, args=(9,))) threads[-1].start() for t in threads: t.join() print('都下班了,關燈關門走人') if __name__ == '__main__': demo()

      3.4 條件 Condition

      兩位小朋友,Hider 和 Seeker,打算玩一個捉迷藏的游戲,規則是這樣的:Seeker 先找個眼罩把眼蒙住,喊一聲“我已經蒙上眼了”;聽到消息后,Hider 就找地方藏起來,藏好以后,也要喊一聲“我藏好了,你來找我吧”;Seeker 聽到后,也要回應一聲“我來了”,捉迷藏正式開始。各自隨機等了一段時間后,兩位小朋友都憋住了跑了出來。誰先跑出來,就算誰輸。

      # -*- encoding: utf8 -*- import time import threading import random cond = threading.Condition() # 創建條件對象 draw_Seeker = False # Seeker小朋友認輸 draw_Hidwer = False # Hider小朋友認輸 def seeker(): """Seeker小朋友的線程函數""" global draw_Seeker, draw_Hidwer time.sleep(1) # 確保Hider小朋友已經進入消息等待狀態 cond.acquire() # 阻塞時請求資源 time.sleep(random.random()) # 假裝蒙眼需要花費時間 print('Seeker: 我已經蒙上眼了') cond.notify() # 把消息通知到Hider小朋友 cond.wait() # 釋放資源并等待Hider小朋友已經藏好的消息 print('Seeker: 我來了') # 收到Hider小朋友已經藏好的消息后 cond.notify() # 把消息通知到Hider小朋友 cond.release() # 不要再聽消息了,徹底釋放資源 time.sleep(random.randint(3,10)) # Seeker小朋友的耐心只有3-10秒鐘 if draw_Hidwer: print('Seeker: 哈哈,我找到你了,我贏了') else: draw_Seeker = True print('Seeker: 算了,我找不到你,我認輸啦') def hider(): """Hider小朋友的線程函數""" global draw_Seeker, draw_Hidwer cond.acquire() # 阻塞時請求資源 cond.wait() # 如果先于Seeker小朋友請求到資源,則立刻釋放并等待 time.sleep(random.random()) # 假裝找地方躲藏需要花費時間 print('Hider: 我藏好了,你來找我吧') cond.notify() # 把消息通知到Seeker小朋友 cond.wait() # 釋放資源并等待Seeker小朋友開始找人的消息 cond.release() # 不要再聽消息了,徹底釋放資源 time.sleep(random.randint(3,10)) # Hider小朋友的耐心只有3-10秒鐘 if draw_Seeker: print('Hider: 哈哈,你沒找到我,我贏了') else: draw_Hidwer = True print('Hider: 算了,這里太悶了,我認輸,自己出來吧') def demo(): th_seeker = threading.Thread(target=seeker) th_hider = threading.Thread(target=hider) th_seeker.start() th_hider.start() th_seeker.join() th_hider.join() if __name__ == '__main__': demo()

      Python 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:【云駐共創】年輕人如何入場元宇宙?未來已來!
      下一篇:我是一個Java class
      相關文章
      日木av无码专区亚洲av毛片| 亚洲va在线va天堂va不卡下载| 亚洲天堂在线播放| 亚洲婷婷国产精品电影人久久| 亚洲?v女人的天堂在线观看| 亚洲JLZZJLZZ少妇| 亚洲AV无码一区二区三区牲色| 欧洲亚洲国产精华液| 亚洲av永久无码天堂网| 亚洲国产精品美女久久久久| 亚洲欧美国产日韩av野草社区| 亚洲色大成网站www久久九| 亚洲欧美日韩中文无线码| 亚洲av无码专区首页| 久久亚洲AV成人无码国产最大| 亚洲色成人四虎在线观看| 亚洲狠狠色丁香婷婷综合| 日日摸日日碰夜夜爽亚洲| 少妇亚洲免费精品| 亚洲视频在线免费| 亚洲日韩国产精品第一页一区| 亚洲国产精品福利片在线观看 | 亚洲成AV人片一区二区密柚| 国产精品亚洲а∨无码播放| 久久久久久久尹人综合网亚洲| 亚洲AV无码精品无码麻豆| 久久夜色精品国产噜噜亚洲AV| 亚洲国语精品自产拍在线观看| 亚洲成综合人影院在院播放| 亚洲AV综合色区无码二区爱AV| 亚洲欧好州第一的日产suv| 国产精品亚洲综合网站| 中文字幕亚洲综合久久男男| 亚洲成a人片在线观看无码专区| 久久精品亚洲中文字幕无码麻豆| 亚洲精品永久www忘忧草| 亚洲最新在线视频| 亚洲欧美第一成人网站7777 | 国产成人亚洲午夜电影| 国产AV无码专区亚洲AV手机麻豆| 国产aⅴ无码专区亚洲av麻豆|