python多線程併發

来源:https://www.cnblogs.com/linuxchao/archive/2019/06/23/linuxchao-thread.html
-Advertisement-
Play Games

單線程執行 python的內置模塊提供了兩個內置模塊:thread和threading,thread是源生模塊,threading是擴展模塊,在thread的基礎上進行了封裝及改進。所以只需要使用threading這個模塊就能完成併發的測試 實例 創建並啟動一個單線程 執行結果 其實單線程的執行結果 ...


單線程執行

python的內置模塊提供了兩個內置模塊:thread和threading,thread是源生模塊,threading是擴展模塊,在thread的基礎上進行了封裝及改進。所以只需要使用threading這個模塊就能完成併發的測試

實例

創建並啟動一個單線程

import threading


def myTestFunc():
    print("我是一個函數")

t = threading.Thread(target=myTestFunc)  # 創建一個線程
t.start()  # 啟動線程

執行結果

C:\Python36\python.exe D:/MyThreading/myThread.py
我是一個線程函數

Process finished with exit code 0

其實單線程的執行結果和單獨執行某一個或者某一組函數結果是一樣的,區別隻在於用線程的方式執行函數,而線程是可以同時執行多個的,函數是不可以同時執行的。

多線程執行

上面介紹了單線程如何使用,多線程只需要通過迴圈創建多個線程,並迴圈啟動線程執行就可以了

實例

import threading
from datetime import datetime


def thread_func():  # 線程函數
    print('我是一個線程函數', datetime.now())


def many_thread():
    threads = []
    for _ in range(10):  # 迴圈創建10個線程
        t = threading.Thread(target=thread_func)
        threads.append(t)
    for t in threads:  # 迴圈啟動10個線程
        t.start()


if __name__ == '__main__':
    many_thread()

執行結果

C:\Python36\python.exe D:/MyThreading/manythread.py
我是一個線程函數 2019-06-23 16:54:58.205146
我是一個線程函數 2019-06-23 16:54:58.205146
我是一個線程函數 2019-06-23 16:54:58.206159
我是一個線程函數 2019-06-23 16:54:58.206159
我是一個線程函數 2019-06-23 16:54:58.206159
我是一個線程函數 2019-06-23 16:54:58.207139
我是一個線程函數 2019-06-23 16:54:58.207139
我是一個線程函數 2019-06-23 16:54:58.207139
我是一個線程函數 2019-06-23 16:54:58.208150
我是一個線程函數 2019-06-23 16:54:58.208150

Process finished with exit code 0

通過迴圈創建10個線程,並且執行了10次線程函數,但需要註意的是python的併發並非絕對意義上的同時處理,因為啟動線程是通過迴圈啟動的,還是有先後順序的,通過執行結果的時間可以看出還是有細微的差異,但可以忽略不記。當然如果線程過多就會擴大這種差異。我們啟動500個線程看下程式執行時間

實例

import threading
from datetime import datetime


def thread_func():  # 線程函數
    print('我是一個線程函數', datetime.now())


def many_thread():
    threads = []
    for _ in range(500):  # 迴圈創建500個線程
        t = threading.Thread(target=thread_func)
        threads.append(t)
    for t in threads:  # 迴圈啟動500個線程
        t.start()


if __name__ == '__main__':
    start = datetime.today().now()
    many_thread()
    duration = datetime.today().now() - start
    print(duration)

執行結果

0:00:00.111657

Process finished with exit code 0

500個線程共執行了大約0.11秒

那麼針對這種問題我們該如何優化呢?我們可以創建25個線程,每個線程執行20次線程函數,這樣在啟動下一個線程的時候,上一個線程已經在迴圈執行了,這樣就大大減少了併發的時間差異

優化

import threading
from datetime import datetime
def thread_func(): # 線程函數 print('我是一個線程函數', datetime.now()) def execute_func(): for _ in range(20): thread_func() def many_thread(): start = datetime.now() threads = [] for _ in range(25): # 迴圈創建500個線程 t = threading.Thread(target=execute_func) threads.append(t) for t in threads: # 迴圈啟動500個線程 t.start() duration = datetime.now() - start print(duration) if __name__ == '__main__': many_thread()

輸出結果(僅看程式執行間隔)

0:00:00.014959

Process finished with exit code 0

後面的優化執行500次併發一共花了0.014秒。比未優化前的500個併發快了幾倍,如果線程函數的執行時間比較長的話,那麼這個差異會更加顯著,所以大量的併發測試建議使用後者,後者比較接近同時“併發”

守護線程

多線程還有一個重要概念就是守護線程。那麼在這之前我們需要知道主線程和子線程的區別,之前創建的線程其實都是main()線程的子線程,即先啟動主線程main(),然後執行線程函數子線程。

那麼什麼是守護線程?即當主線程執行完畢之後,所有的子線程也被關閉(無論子線程是否執行完成)。預設不設置的情況下是沒有守護線程的,主線程執行完畢後,會等待子線程全部執行完畢,才會關閉結束程式。

但是這樣會有一個弊端,當子線程死迴圈了或者一直處於等待之中,則程式將不會被關閉,被被無限掛起,我們把上述的線程函數改成迴圈10次, 並睡眠2秒,這樣效果會更明顯

import threading
from datetime import datetime
import time
def thread_func(): # 線程函數
   time.sleep(2)
i = 0 while(i < 11): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 迴圈創建500個線程 t = threading.Thread(target=thread_func) threads.append(t) for t in threads: # 迴圈啟動500個線程 t.start() if __name__ == '__main__': many_thread() print("thread end")

執行結果

C:\Python36\python.exe D:/MyThreading/manythread.py
thread end
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546

Process finished with exit code 0

根據上述結果可以看到主線程列印了“thread end”之後(主線程結束),子線程還在繼續執行,並未隨著主線程的結束而結束

下麵我們通過 setDaemon方法給子線程添加守護線程,我們把迴圈改為死迴圈,再來看看輸出結果(註意守護線程要加在start之前)

import threading
from datetime import datetime
def thread_func():  # 線程函數
    i = 0
    while(1):
        print(datetime.now())
        i += 1

def many_thread():
    threads = []
    for _ in range(10):  # 迴圈創建500個線程
        t = threading.Thread(target=thread_func)
        threads.append(t)
        t.setDaemon(True)  # 給每個子線程添加守護線程
    for t in threads:  # 迴圈啟動500個線程
        t.start()


if __name__ == '__main__':
    many_thread()
    print("thread end")

輸出結果

2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.565529
2019-06-23 19:12:35.565529
2019-06-23 19:12:35.565529
thread end

Process finished with exit code 0

通過結果我們可以發現,主線程關閉之後子線程也會隨著關閉,並沒有無限的迴圈下去,這就像程式執行到一半強制關閉執行一樣,看似暴力卻很有用,如果子線程發送一個請求未收到請求結果,那不可能永遠等下去,這時候就需要強制關閉。所以守護線程解決了主線程和子線程關閉的問題。

阻塞線程

上面說了守護線程的作用,那麼有沒有別的方法來解決上述問題呢? 其實是有的,那就是阻塞線程,這種方式更加合理,使用join()方法阻塞線程,讓主線程等待子線程執行完成之後再往下執行,再關閉所有子線程,而不是只要主線程結束,不管子線程是否執行完成都終止子線程執行。下麵我們給子線程添加上join()(主要join要加到start之後)

import threading
from datetime import datetime
import time


def thread_func():  # 線程函數
    time.sleep(1)
    i = 0
    while(i < 11):
        print(datetime.now())
        i += 1

def many_thread():
    threads = []
    for _ in range(10):  # 迴圈創建500個線程
        t = threading.Thread(target=thread_func)
        threads.append(t)
        t.setDaemon(True)  # 給每個子線程添加守護線程
    for t in threads:  # 迴圈啟動500個線程
        t.start()
    for t in threads:
        t.join()  # 阻塞線程

if __name__ == '__main__':
    many_thread()
    print("thread end")

執行結果

程式會一直執行,但是不會列印“thread end”語句,因為子線程並未結束,那麼主線程就會一直等待。

疑問:有人會覺得這和什麼都不設置是一樣的,其實會有一點區別的,從守護線程和線程阻塞的定義就可以看出來,如果什麼都沒設置,那麼主線程會先執行完畢列印後面的“thread end”,而等待子線程執行完畢。兩個都設置了,那麼主線程會等待子線程執行結束再繼續執行。

而對於死迴圈或者一直等待的情況,我們可以給join設置超時等待,我們設置join的參數為2,那麼子線程會告訴主線程讓其等待2秒,如果2秒內子線程執行結束主線程就繼續往下執行,如果2秒內子線程未結束,主線程也會繼續往下執行,執行完成後關閉子線程

import threading
from datetime import datetime
import time


def thread_func():  # 線程函數
    time.sleep(1)
    i = 0
    while(1):
        print(datetime.now())
        i += 1

def many_thread():
    threads = []
    for _ in range(10):  # 迴圈創建500個線程
        t = threading.Thread(target=thread_func)
        threads.append(t)
        t.setDaemon(True)  # 給每個子線程添加守護線程
    for t in threads:  # 迴圈啟動500個線程
        t.start()
    for t in threads:
        t.join(2)  # 設置子線程超時2秒

if __name__ == '__main__':
    many_thread()
    print("thread end")

 

輸出結果

你運行程式後會發現,運行了大概2秒的時候,程式會數據“thread end” 然後結束程式執行, 這就是阻塞線程的意義,控制子線程和主線程的執行順序

總結

最好呢,再次說一下守護線程和阻塞線程的定義

守護線程:子線程會隨著主線程的結束而結束,無論子線程是否執行完畢

阻塞線程:主線程會等待子線程的執行結束,才繼續執行

 


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

-Advertisement-
Play Games
更多相關文章
  • 單表查詢的語法及關鍵字執行的優先順序 單表查詢語法 關鍵字執行的優先順序 1. 找到表: from 2. 拿著where指定的約束條件,去文件 / 表中取出一條條記錄 3. 將取出的一條條記錄進行分組group by , 如果沒有group by ,則整體作為一組 4. 執行select (distin ...
  • 新聞 "Azure Notebook概覽" "SpecFlow 3就在這裡了!" "使用新的Try .NET模版創建互動式文檔" "逐漸演化的.NET Core框架" "Dylan與Linebreakers Oslo 2019" 視頻及幻燈片 "F MonoGame平臺游戲系列:平鋪背景" "我愛F ...
  • metaclass 的超越變形特性有什麼用? 來看yaml的實例: import yaml class Monster(yaml.YAMLObject): yaml_tag = u'!Monster' def __init__(self, name, hp, ac, attacks): self.n ...
  • django 路由系統 [TOC] 路由是什麼? Django 1.11版本 URLConf官方文檔 基本格式: 示例: 參考官方文檔 2.0版本中re_path和1.11版本的url是一樣的用法。 正則表達式 詳解 補充說明 分組 命名分組 (建議使用命名分組) 小結 指定預設值 include其 ...
  • 6.23 自我總結 1.描述符\_\_get\_\_,\_\_set\_\_,\_\_delete\_\_ 描述符是什麼:描述符本質就是一個新式類,在這個新式類中,至少實現了___\_get\_\_(),\_\_set\_\_(),\_\_delete\_\_()中的一個,這也被稱為描述符協議 __ ...
  • JFxUtils "項目地址" 介紹 這是一個JFX的工具庫,Intent可以簡單地實現打開一個新視窗並傳遞數據,DialogBuilder可以簡單地生成對話框,MyUtils有些常用的功能 使用 與`JavaFxTemplate JavaFxTemplate`模板 JavaFxTemplate模版 ...
  • 6.23 自我總結 面向對象的高階 1.isinstance/type/issubclass 1.type 顯示對象的類,但是不會顯示他的父類 2.isinstance 會顯示的對象的類,也會去找對象的父類,填寫參數是對象,類isinstance(對象,類)如果對象屬於後面的類會報Ture,反之Fa ...
  • 摘要: 主頁面的搭建(導航條下麵的區域) 個人站點 側邊欄分類展示 側邊欄標簽展示 側邊欄日期歸檔 文章詳情頁 文章內容 文章點贊點踩 文章評論 側邊欄分類展示 側邊欄標簽展示 側邊欄日期歸檔 文章內容 文章點贊點踩 文章評論 一、主頁面home.html的搭建(進一步完善) home.html頁面 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...