python多線程創建與使用(轉)

来源:https://www.cnblogs.com/xhnxhnu/archive/2018/10/01/9736011.html
-Advertisement-
Play Games

原文: "http://codingpy.com/article/python 201 a tutorial on threads/" 創建多線程 創建多線程主要有2種方式。 使用threading.Thread函數 繼承threading類 1. 使用threading.Thread函數 impo ...


原文:http://codingpy.com/article/python-201-a-tutorial-on-threads/

創建多線程

創建多線程主要有2種方式。

  • 使用threading.Thread函數
  • 繼承threading類

1. 使用threading.Thread函數

import threading

def tom(number):
    print  threading.currentThread().getName()
    print number

if __name__ == "__main__":
    number = ["zero", "one", "two", "three", "four"]
    sex = ["man", "woman"]
    for i in range(5):
        th = threading.Thread(target=tom, args=(number[i],))
        # th.setName('mythread')
        # print th.getName()
        th.start()

說明:Thread()函數有2個參數,一個是target,內容為子線程要執行的函數名稱;另一個是args,內容為需要傳遞的參數。Args 參數看起來有些奇怪,那是因為我們需要傳遞一個序列給tom函數,但它只接受一個變數,所以我們把逗號放在尾部來創建只有一個參數的序列。創建完子線程,將會返回一個對象,調用對象的start方法,可以啟動子線程。

當你運行以上這段代碼,會得到以下輸出:

Thread-1
zero
Thread-2
one
Thread-3
two
Thread-4
three
Thread-5
four

線程對象的方法:

  • Start() 開始線程的執行

  • Run() 定義線程的功能的函數

  • Join(timeout=None) 程式掛起,直到線程結束;如果給了timeout,則最多阻塞timeout秒

  • getName() 返回線程的名字

  • setName() 設置線程的名字

  • isAlive() 布爾標誌,表示這個線程是否還在運行

  • isDaemon() 返回線程的daemon標誌

  • setDaemon(daemonic) 把線程的daemon標誌設為daemonic(一定要在start()函數前調用)

  • t.setDaemon(True) 把父線程設置為守護線程,當父進程結束時,子進程也結束

2. 繼承threading類

import  threading

class mythread(threading.Thread):
    def __init__(self,number):
        threading.Thread.__init__(self)
        self.number = number

    def run(self):
        print threading.current_thread().getName()
        print self.number

if __name__ == "__main__":
    for i in range(5):
        th = mythread(i)
        th.start()

當你運行以上這段代碼,會得到以下輸出:

Thread-1
0
Thread-2
1
Thread-3
2
Thread-4
3
Thread-5
4

當然,通常情況下你不會希望輸出列印到標準輸出。如果不幸真的這麼做了,那麼最終的顯示效果將會非常混亂。你應該使用 Python 的 logging 模塊。它是線程安全的,並且表現出色。讓我們用 logging 模塊修改上面的例子並且給我們的線程命名。代碼如下:

import threading
import logging

def get_logger():
    #創建一個被設置為調試級別的日誌記錄器
    logger = logging.getLogger("mylogger")
    logger.setLevel(logging.DEBUG)
    
    #設置每行日誌的格式。格式包括時間戳、線程名、日誌記錄級別以及日誌信息
    fh = logging.FileHandler("threading.log")
    fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)
    
    logger.addHandler(fh)
    return logger


def tom(number, logger):
    logger.debug(number)

if __name__ == "__main__":
    logger = get_logger()
    number = ["zero", "one", "two", "three", "four"]
    sex = ["man", "woman"]
    for i in range(5):
        th = threading.Thread(target=tom, args=(number[i],logger))
        # th.setName('mythread')
        # print th.getName()
        th.start()

通過繼承的方法:

import  threading
import logging


class mythread(threading.Thread):
    def __init__(self,number,logger):
        threading.Thread.__init__(self)
        self.number = number
        self.logger = logger

    def run(self):
        self.logger.debug("calling-thread")
        tom(self.number, self.logger)


def get_logger():
    logger = logging.getLogger("mylogger")
    logger.setLevel(logging.DEBUG)
    
    fh = logging.FileHandler("threading.log")
    fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)
    
    logger.addHandler(fh)
    return logger


def tom(number, logger):
    

if __name__ == "__main__":
    logger = get_logger()
    for i in range(5):
        th = mythread(i, logger)
        th.start()

在 tom 函數中,我們把 print 語句換成 logging 語句。你會註發現,在創建線程時,我們給 doubler 函數傳入了 logger 對象。這樣做的原因是,如果在每個線程中實例化 logging 對象,那麼將會產生多個 logging 單例(singleton),並且日誌中將會有很多重覆的內容

線程鎖與線程同步

由於物理上得限制,各CPU廠商在核心頻率上的比賽已經被多核所取代。為了更有效的利用多核處理器的性能,就出現了多線程的編程方式,而隨之帶來的就是線程間數據一致性和狀態同步的困難。解決多線程之間數據完整性和狀態同步的最簡單方法自然就是加鎖。鎖由 Python 的 threading 模塊提供,並且它最多被一個線程所持有。當一個線程試圖獲取一個已經鎖在資源上的鎖時,該線程通常會暫停運行,直到這個鎖被釋放。

有兩種方式為線程加鎖:

  1. try...finally

  2. with

代碼如下:

import  threading
import logging

lock = threading.Lock()
class mythread(threading.Thread):
    def __init__(self,number,logger):
        threading.Thread.__init__(self)
        self.number = number
        self.logger = logger
    
    def run(self):
        lock.acquire()
        try:
            self.logger.debug("calling-thread")
            tom(self.number, self.logger)
        finally:
            lock.release()

def get_logger():
    logger = logging.getLogger("mylogger")
    logger.setLevel(logging.DEBUG)
    
    fh = logging.FileHandler("threading.log")
    fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)
    
    logger.addHandler(fh)
    return logger


def tom(number, logger):
    with lock:
        logger.debug(number)



if __name__ == "__main__":
    logger = get_logger()
    for i in range(5):
        with lock:
            th = mythread(i, logger)
            th.start()

當你真正運行這段代碼時,你會發現它只是掛起了。究其原因,是因為我們只告訴 threading 模塊獲取鎖。所以當我們調用第一個函數時,它發現鎖已經被獲取,隨後便把自己掛起了,直到鎖被釋放,然而這將永遠不會發生。

真正的解決辦法是使用重入鎖(Re-Entrant Lock)。threading 模塊提供的解決辦法是使用 RLock 函數。即把 lock = threading.lock() 替換為 lock = threading.RLock(),然後重新運行代碼,現在代碼就可以正常運行了。

線程通信

某些情況下,你會希望線程之間互相通信。就像先前提到的,你可以通過創建 Event 對象達到這個目的。但更常用的方法是使用隊列(Queue)。在我們的例子中,這兩種方式都會有所涉及。下麵讓我們看看到底是什麼樣子的:

import threading

import Queue


def creator(data, q):
"""
生成用於消費的數據,等待消費者完成處理
"""
    print('Creating data and putting it on the queue')
    for item in data:
        evt = threading.Event()
        q.put((item, evt))
        
        print('Waiting for data to be doubled')
        evt.wait()


def my_consumer(q):
"""
消費部分數據,並做處理

這裡所做的只是將輸入翻一倍

"""
while True:
    data, evt = q.get()
    print('data found to be processed: {}'.format(data))
    processed = data * 2
    print(processed)
    evt.set()
    q.task_done()


if __name__ == '__main__':
    q = Queue()
    data = [5, 10, 13, -1]
    thread_one = threading.Thread(target=creator, args=(data, q))
    thread_two = threading.Thread(target=my_consumer, args=(q,))
    thread_one.start()
    thread_two.start()
    
    q.join()

讓我們掰開揉碎分析一下。首先,我們有一個創建者(creator)函數(亦稱作生產者(producer)),我們用它來創建想要操作(或者消費)的數據。然後用另外一個函數 my_consumer 來處理剛纔創建出來的數據。Creator 函數使用 Queue 的 put 方法向隊列中插入數據,消費者將會持續不斷的檢測有沒有更多的數據,當發現有數據時就會處理數據。Queue 對象處理所有的獲取鎖和釋放鎖的過程,這些不用我們太關心。

在這個例子中,先創建一個列表,然後創建兩個線程,一個用作生產者,一個作為消費者。你會發現,我們給兩個線程都傳遞了 Queue 對象,這兩個線程隱藏了關於鎖處理的細節。隊列實現了數據從第一個線程到第二個線程的傳遞。當第一個線程把數據放入隊列時,同時也傳遞一個 Event 事件,緊接著掛起自己,等待該事件結束。在消費者側,也就是第二個線程,則做數據處理工作。當完成數據處理後就會調用 Event 事件的 set 方法,通知第一個線程已經把數據處理完畢了,可以繼續生產了。

最後一行代碼調用了 Queue 對象的 join 方法,它會告知 Queue 等待所有線程結束。當第一個線程把所有數據都放到隊列中,它也就運行結束了。


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

-Advertisement-
Play Games
更多相關文章
  • ng alain 是一個企業級中後臺前端/設計解決方案腳手架,我們秉承 Ant Design 的設計價值觀,目標也非常簡單,希望在Angular上面開發企業後臺更簡單、更快速。隨著『設計者』的不斷反饋,將持續迭代,逐步沉澱和總結出更多設計模式和相應的代碼實現,闡述中後臺產品模板/組件/業務場景的最佳 ...
  • 現在基本項目都是實行前後端分離的原則,不管是ng 或者是vue 在開發中都無法避免跨域的這個問題 本人剛上手第一個vue項目,在調用api的時候出現了跨域的這個問題 這是封裝好一個簡單的post 請求 http.js 在調用的時候 先import 這個http 後 this.http.post(ur ...
  • 在vue項目中添加ESLint,new 一個 Promise 一直顯示錯誤 :Expected the Promise rejection reason to be an Error 正常來說new 一個Promise 是這樣寫的,感覺沒啥問題。 但是項目中添加了 ESLint 你必須按照他的規範來 ...
  • 一、題目示例: 思路: 1、匹配屬性名字元串中的大寫字母和數字 2、通過匹配後的lastIndex屬性獲取匹配到的大寫字母和數字的位置 3、判斷大寫字母的位置是否為首位置以及lastIndex是否為0,為0則表示匹配結束 4、將存放位置的數組進行從小到大排序,排序後將屬性名按照字元串的slice方法 ...
  • 1、為什麼使用em em也是css中的一種單位,和px類似。很多人會疑惑為什麼有了px之後還要使用em,而且em使用起來相對於px來講比較麻煩。 em主要是應用於彈性佈局,下麵給出一個小慄子說明em的強大之處 <!DOCTYPE html> <html lang="en"> <head> <meta ...
  • Node.js 多版本安裝 Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine(Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境)。 Node.js 使用了一個事件驅動、 ...
  • 前端工程化—部署 使用腳手架創建初始項目,在本地搭建開發伺服器進行項目開發。編碼完成後,經過構建生成目標環境可用的代碼,到此階段的所有工作都屬於開發環節。下一步的工作需要將代碼部署到指定的環境中,方便進一步的聯調測試工作。 在部署一些個人項目或者小團隊的項目時,可能就是使用一些工具(如FTP上傳工具 ...
  • 2018-10-02 C語言錯題集 main 是一個合法的標識符嗎? 答:是,main 是函數的標識符名稱。 如果有符號常量定義如下: 那麼請問代碼中 F(3+2) 的值等於多少? 答:F(3+2) == 2*3+2 == 8,註意,巨集定義是在程式編譯時先進行的預處理,做法是直接將標識符替換為常量, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...