Python3 多線程編程 - 學習筆記

来源:https://www.cnblogs.com/aduner/archive/2019/10/11/11645102.html
-Advertisement-
Play Games

線程 什麼是線程 官方定義: 線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。 說人話: 假如 進程 是保潔公司, 線程 就是公司的員工。當公司接到 ...


線程

什麼是線程

  • 官方定義:

    線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。

  • 說人話:

    假如進程是保潔公司,線程就是公司的員工。當公司接到任務時,幹活的是員工,而進程負責分配員工。可以好幾個員工擦一塊玻璃,也可以一個員工收拾一個屋子。

特點

  • 獨立調度和分派的基本單位

    一個公司里至少得有一個員工,才能幹活。公司分配各項工作給團隊,最後團隊還是會分配給個人。

  • 輕型實體

    每個線程占用的系統資源非常少。

  • 可併發執行

    多個線程可以同時工作,就像公司里的員工可以一切幹活。

  • 共用進程資源

    所有的線程共用該進程所擁有的資源。

    例如: 所有線程地址空間都相同(進程的地址空間),就好比一家公司的所有員工的工作地址都為公司的所在地。

線程與進程的關係

繼續用保潔公司舉例子

  • 公司本身是進程,公司里又很多員工,每個員工都是一個線程,同時公司里還有很多共用的資源,比如:掃把、墩布、毛巾、飲水機等。
  • 但是與現實模型不同的是,這些人是由多個 cpu 控制的,例如 4 個 cpu 對應 40 個人,cpu 需要切換控制。
  • 真正執行工作的是公司員工,也就是進程的任務靠線程執行
  • 這些人可以並行工作,處理事情。也就是多線程可以同時運行。
  • 當大家訪問公共資源的時候會有衝突,例如:都要出去擦玻璃,就剩一條毛巾了,這時就需要排隊。在進程中就叫做上鎖。

Python3中的多線程

全局解釋器鎖(GIL)

GIL是啥?

  • GIL並不是Python語言的特性,它是在現實Python解釋器時引用的一個概念。

  • GIL只在CPython解釋器上存在。作用是保證同一時間內只有一個線程在執行。

  • 解決解釋器中多個線程的競爭資源問題

GIL對Python程式有啥影響?

  • Python中同一時刻有且只有一個線程會執行。

    因此,Python中的多線程並不算是真正意義上的多線程。
  • Python中的多個線程由於GIL鎖的存在無法利用多核CPU。

    因此,Python中的多線程不適合電腦密集型的程式。

    • 計算密集型程式

      計算密集型又叫CUP密集型,這類程式絕大部分運行時間都消耗在CPU計算上,這個時候,不論你開多少線程,他用的時間都是這麼多,甚至比原來時間還長,因為GIL一個時刻只讓你執行一個線程,大部分計算密集型任務你分了很多線程但是依然會按照代碼順序線性執行。分很多線程沒有什麼改善,反而因為代碼的冗雜可能更加慢。

    • IO密集型程式

      IO密集型顧名思義就是主要進行I/O操作,90%以上的時間都花費在網路、硬碟、輸入輸出上了,CPU執行完命令之後其實就沒事幹了,就可以釋放記憶體來執行下一條命令了,不用讓CPU在那乾等著,這樣就能大大提高程式的運行效率。

改善GIL產生的問題

  • 使用更高版本的解釋器,優化對Python的解釋
  • 變更解釋器,如(JPython),但可能因為相對小眾,支持的模塊相對較少,開發效率變低
  • 用多進程方案替代多線程

Python3關於多線程的模塊

Python的標準庫提供了兩個模塊:_thread和threading

  • _thread

    在Python3之前為thread,有於存在缺陷,不建議使用,在Python3中封裝成_thread

  • threading

    thread的繼承者,絕大多數情況下使用threading就夠了。

多線程使用

  • 直接調用

    把一個函數傳入創建Thread實例,然後調用start()開始執行

    import threading
    import time
    def loop():
        #threading.current_thread().name獲取當前線程的名字
        print(f"線程( {threading.current_thread().name} )正在執行……")
        num = 0
        while num < 5:
            num += 1
            print(f"線程( {threading.current_thread().name} )>>> {num}")
            time.sleep(1)
        print(f"線程( {threading.current_thread().name}) 執行結束")
    
    if __name__ == '__main__':
        print(f"線程( {threading.current_thread().name} )正在執行……")
        #創建線程實體,target導入的函數,name線程的名字,初始為Tread1,之後類推2,3,4
        t = threading.Thread(target=loop,name='LoopThread')
        #啟動線程
        t.start()
        #等待多線程結束
        t.join()
      print(f"線程( {threading.current_thread().name} )執行結束")
    

    執行結果如下:

    線程( MainThread )正在執行……
    線程( LoopThread )正在執行……
    線程( LoopThread )>>> 1
    線程( LoopThread )>>> 2
    線程( LoopThread )>>> 3
    線程( LoopThread )>>> 4
    線程( LoopThread )>>> 5
    線程( LoopThread) 執行結束
    線程( MainThread )執行結束
  • 繼承自threading.Thread調用

    • 直接繼承Thread
    • 重寫run函數,run函數代表的是真正執行的功能
    • 類實例可以直接運行
    
    import threading
    import time
    # 1. 類需要繼承自threading.Thread
    class MyThread(threading.Thread):
        def __init__(self, arg):
            super(MyThread, self).__init__()
            self.arg = arg
    
        # 2 必須重寫run函數,run函數代表的是真正執行的功能
        def  run(self):
            time.sleep(2)
            print(f"Run  >>>  {self.arg}")
    
    for i in range(1,4):
        t = MyThread(i)
        t.start()
        t.join()
    
    print("End")
    

    執行結果如下:

    Run  >>>  1
    Run  >>>  2
    Run  >>>  3
    End
  • 守護線程

    • 不設置守護線程

      import time
      import threading
      
      def fun():
          print("啟動Fun")
          time.sleep(2)
          print("結束Fun")
      
      print("啟動Main")
      t = threading.Thread(target=fun)
      t.start()
      time.sleep(1)
      print("結束Main")

      運行結果

      啟動Main
      啟動Fun
      結束Main
      結束Fun
    • 設置守護線程

      import time
      import threading
      
      def fun():
          print("啟動Fun")
          time.sleep(2)
          print("結束Fun")
      
      print("啟動Main")
      t = threading.Thread(target=fun)
      t.setDaemon(True)
      t.start()
      time.sleep(1)
      print("結束Main")

      運行結果

      啟動Main
      啟動Fun
      結束Main

      如果你設置一個線程為守護線程,,就表示你認為此線程不重要,在進程退出的時候,不用等待這個線程即可退出。

      thread.setDaemon(True|False)表示此線程是否為守護線程。但此語句必須加在thread.start()之前
  • 常用函數

    • threading.enumerate():

      返回一個包含正在運行的線程的list
    • threading.activeCount():

      返回正在運行的線程數量,效果跟 len(threading.enumerate)相同
    • thr.setName(): 給線程設置名字

      thr.getName(): 得到線程的名字

      • thr表示線程實例,如t1.getName()

      • thr.getName()獲取指定線程的名字,threading.current_thread().name獲取當前線程的名字

共用變數

多線程同時訪問同一變數時,會產生共用變數的問題,造成變數衝突產生問題。

  • 實例

    執行多線程,對同一變數進行加減操作,代碼如下。

    import threading
    
    sum = 0
    loopSum = 1000000
    
    def myAdd():
        global  sum, loopSum
        for i in range(1, loopSum):
            sum += 1
    
    def myMinu():
        global  sum, loopSum
        for i in range(1, loopSum):
            sum -= 1
    
    if __name__ == '__main__':
        print(f"Starting ....{sum}")
    
        t1 = threading.Thread(target=myAdd, args=())
        t2 = threading.Thread(target=myMinu, args=())
    
        t1.start()
        t2.start()
    
        t1.join()
        t2.join()
    
        print(f"Done .... {sum}")
    

    兩次分別如下執行結果如下:

    Starting ....0
    Done .... -278763
    Starting ....0
    Done .... 662850

    可以發現,運算結果與我們傳統認知不同。原因是Python在內部實現運算時是一個複雜的過程,同時對sum進行修改時產生的相互干擾,故出現未知錯誤,導致結果出錯。

  • 解決方法:上鎖

    鎖(Lock):是一個標誌,表示一個線程在占用一些共用資源.

    • 那些資源需要上鎖?

      需要被共用使用的,可能產生使用衝突的

    • 註意:

      避免產生死鎖,導致程式陷入死迴圈

    • 線程安全問題:
      • 如果一個資源,他對於多線程來講,不用加鎖也不會引起任何問題,則稱為線程安全。
      • 線程不安全變數類型: list, set, dict
      • 線程安全變數類型: queue

    使用案例,代碼如下:

    import threading
    
    sum = 0
    loopSum = 1000000
    lock = threading.Lock()
    def myAdd():
        global  sum, loopSum
        for i in range(1, loopSum):
            # 上鎖,申請鎖
            lock.acquire()
            sum += 1
            # 釋放鎖
            lock.release()
    
    def myMinu():
        global  sum, loopSum
    
        for i in range(1, loopSum):
            lock.acquire()
            sum -= 1
            lock.release()
    
    if __name__ == '__main__':
        print(f"Starting ....{sum}")
    
        t1 = threading.Thread(target=myAdd, args=())
        t2 = threading.Thread(target=myMinu, args=())
    
        t1.start()
        t2.start()
    
        t1.join()
        t2.join()
    
        print(f"Done .... {sum}")

    執行結果:

    Starting ....0
    Done .... 0

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、生成器簡介在python中,生成器是根據某種演算法邊迴圈邊計算的一種機制。主要就是用於操作大量數據的時候,一般我們會將操作的數據讀入記憶體中處理,可以電腦的記憶體是比較寶貴的資源,我認為的當要處理的數據超過記憶體四分之一的大小時就應該使用生成器。 二、生成器有什麼特點?1.和傳統的容器相比,生成器更節 ...
  • 在開始本篇文章之前,我想你對SpringCloud和SpringBoot的基本使用已經比較熟悉了,如果不熟悉的話可以參考我之前寫過的文章 本篇文章的源碼基於SpringBoot2.0,SpringCloud的Finchley.RELEASE 註解 我們知道,在使用Eureka作為註冊中心的時候,我們 ...
  • 位運算在redis中非常的方便使用,並且理由利用這個可以實現很多特殊的功能。這也迫使我去研究更多的redis提供的函數,只有研究的多,思路才能夠更加開放。今天我就對strings下麵的幾個函數進行了測試,也收穫頗豐。 使用setBit和bitCount可以實現用戶活躍天數的統計,大體的思路如下:我們 ...
  • #include<stdio.h>int main(){ double i; double bonus1,bonus2,bonus4,bonus6,bonus10,bonus; printf("你的利潤是:\n"); scanf("%lf",&i); bonus1=100000*0.1; bonus ...
  • Thymeleaf中有許多內置對象,可以在模板中實現各種功能。 下麵有幾個基本對象。 Web對象常用有:request、session、servletContext。 Thymeleaf提供了幾個內置變數param、session、application,分別可以訪問請求參數、session屬... ...
  • 手工操作 —— 穿孔卡片 1946年第一臺電腦誕生--20世紀50年代中期,電腦工作還在採用手工操作方式。此時還沒有操作系統的概念。 程式員將對應於程式和數據的已穿孔的紙帶(或卡片)裝入輸入機,然後啟動輸入機把程式和數據輸入電腦記憶體,接著通過控制台開關啟動程式針對數據運行;計算完畢,印表機輸出 ...
  • 一、基本介紹 logging 模塊是python自帶的一個包,因此在使用的時候,不必安裝,只需要import即可。 logging有 5 個不同層次的日誌級別,可以將給定的 logger 配置為這些級別: DEBUG:詳細信息,用於診斷問題。Value=10。 INFO:確認代碼運行正常。Value ...
  • 1. 多線程 如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程式每次運行結果和單線程運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是線程安全的。 我們通過一個案例,演示線程的安全問題: 電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “功夫熊貓3”,本次電影的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...