Python線程threading(Thread類)

前言

幾乎所有的操作系統都支持同時運行多個任務,每個任務通常是一個程序,每一個運行中的程序就是一個進程,即進程是應用程序的執行實例。現代的操作系統幾乎都支持多進程並發執行。註意,並發和並行是兩個概念,並行指在同一時刻有多條指令在多個處理器上同時執行並發是指在同一時刻隻能有一條指令執行,但多個進程指令被快速輪換執行,使得在宏觀上具有多個進程同時執行的效果。
例如,程序員一邊開著開發工具在寫程序,一邊開著參考手冊備查,同時還使用電腦播放音樂……除此之外,每臺電腦運行時還有大量底層的支撐性程序在運行……這些進程看上去像是在同時工作。但事實的真相是,對於一個 CPU 而言,在某個時間點它隻能執行一個程序。也就是說,隻能運行一個進程,CPU 不斷地在這些進程之間輪換執行。那麼,為什麼用戶感覺不到任何中斷呢?這是因為相對人的感覺來說,CPU 的執行速度太快瞭(如果啟動的程序足夠多,則用戶依然可以感覺到程序的運行速度下降瞭)。所以,雖然 CPU 在多個進程之間輪換執行,但用戶感覺到好像有多個進程在同時執行。

線程是進程的組成部分,一個進程可以擁有多個線程。在多線程中,會有一個主線程來完成整個進程從開始到結束的全部操作,而其他的線程會在主線程的運行過程中被創建或退出。當進程被初始化後,主線程就被創建瞭,對於絕大多數的應用程序來說,通常僅要求有一個主線程,但也可以在進程內創建多個順序執行流,這些順序執行流就是線程。當一個進程裡隻有一個線程時,叫作單線程。超過一個線程就叫作多線程。每個線程必須有自己的父進程,且它可以擁有自己的堆棧、程序計數器和局部變量,但不擁有系統資源,因為它和父進程的其他線程共享該進程所擁有的全部資源。線程可以完成一定的任務,可以與其他線程共享父進程中的共享變量及部分環境,相互之間協同完成進程所要完成的任務。多個線程共享父進程裡的全部資源,會使得編程更加方便,需要註意的是,要確保線程不會妨礙同一進程中的其他線程。線程是獨立運行的,它並不知道進程中是否還有其他線程存在。線程的運行是搶占式的,也就是說,當前運行的線程在任何時候都可能被掛起,以便另外一個線程可以運行。多線程也是並發執行的,即同一時刻,Python 主程序隻允許有一個線程執行,這和全局解釋器鎖有關系,後續會做詳細介紹。
一個線程可以創建和撤銷另一個線程,同一個進程中的多個線程之間可以並發運行。從邏輯的角度來看,多線程存在於一個應用程序中,讓一個應用程序可以有多個執行部分同時執行,但操作系統無須將多個線程看作多個獨立的應用,對多線程實現調度和管理以及資源分配,線程的調度和管理由進程本身負責完成。

簡而言之,進程和線程的關系是這樣的:操作系統可以同時執行多個任務,每一個任務就是一個進程,進程可以同時執行多個任務,每一個任務就是一個線程。

Python創建線程 threading

Python 中,有關線程開發的部分被單獨封裝到瞭模塊中,和線程相關的模塊有以下 2 個:
​​​_thread​​​:是 Python 3 以前版本中 thread 模塊的重命名,此模塊僅提供瞭低級別的、原始的線程支持,以及一個簡單的鎖。功能比較有限。正如它的名字所暗示的(以 _ 開頭),一般不建議使用 thread 模塊;
​​​threading​​:Python 3 之後的線程模塊,提供瞭功能豐富的多線程支持,推薦使用。

本節就以 threading 模塊為例進行講解。Python 主要通過兩種方式來創建線程:

  • 使用 threading 模塊中 Thread 類的構造器創建線程。即直接對類 threading.Thread 進行實例化創建線程,並調用實例化對象的 start() 方法啟動線程。
  • 繼承 threading 模塊中的 Thread 類創建線程類。即用 threading.Thread 派生出一個新的子類,將新建類實例化創建線程,並調用其 start() 方法啟動線程。

調用Thread類的構造器創建線程

Thread 類提供瞭如下的 ​​__init__()​​​構造器,可以用來創建線程:​​__init__(self, group=None, target=None, name=None, args=(), kwargs=None, *,daemon=None)​​ 此構造方法中,以上所有參數都是可選參數,即可以使用,也可以忽略。其中各個參數的含義如下:

  • group:指定所創建的線程隸屬於哪個線程組(此參數尚未實現,無需調用);
  • target:指定所創建的線程要調度的目標方法(最常用);
  • args:以元組的方式,為 target 指定的方法傳遞參數;
  • kwargs:以字典的方式,為 target 指定的方法傳遞參數;
  • daemon:指定所創建的線程是否為後代線程。
    這些參數,初學者隻需記住 target、args、kwargs 這 3 個參數的功能即可。

下面程序演示瞭如何使用 Thread 類的構造方法創建一個線程:

import threading
#定義線程要調用的方法,*add可接收多個以非關鍵字方式傳入的參數
def action(*add):
for arc in add:
#調用 getName() 方法獲取當前執行該程序的線程名
print(threading.current_thread().getName() +" "+ arc)
#定義為線程方法傳入的參數
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#創建線程
thread = threading.Thread(target = action,args =my_tuple)

有關 Thread 類提供的和線程有關的方法,可閱讀​ ​Python Thread手冊​​​,由於不是本節重點,這裡不再進行詳細介紹。
由此就創建好瞭一個線程。但是線程需要手動啟動才能運行,threading 模塊提供瞭 start() 方法用來啟動線程。因此在上面程序的基礎上,添加如下語句:thread.start()
再次執行程序,

其輸出結果為:

Thread-1 http://c.biancheng.net/python/
Thread-1 http://c.biancheng.net/shell/
Thread-1 http://c.biancheng.net/java/

可以看到,新創建的 thread 線程(線程名為 Thread-1)執行瞭 action() 函數。
默認情況下,主線程的名字為 MainThread,用戶啟動的多個線程的名字依次為 Thread-1、Thread-2、Thread-3、…、Thread-n 等。

為瞭使 thread 線程的作用更加明顯,可以繼續在上面程序的基礎上添加如下代碼,讓主線程和新創建線程同時工作:

for i in range(5):
print(threading.current_thread().getName())

再次執行程序,其輸出結果為:

MainThreadThread-1 http://c.biancheng.net/python/
MainThreadThread-1 http://c.biancheng.net/shell/
MainThreadThread-1 http://c.biancheng.net/java/
MainThread
MainThread

可以看到,當前程序中有 2 個線程,分別為主線程 MainThread 和子線程 Thread-1,它們以並發方式執行,即 Thread-1 執行一段時間,然後 MainThread 執行一段時間。通過輪流獲得 CPU 執行一段時間的方式,程序的執行在多個線程之間切換,從而給用戶一種錯覺,即多個線程似乎同時在執行。
如果程序中不顯式創建任何線程,則所有程序的執行,都將由主線程 MainThread 完成,程序就隻能按照順序依次執行。

繼承Thread類創建線程類

通過繼承 Thread 類,我們可以自定義一個線程類,從而實例化該類對象,獲得子線程。需要註意的是,在創建 Thread 類的子類時,必須重寫從父類繼承得到的 run() 方法。因為該方法即為要創建的子線程執行的方法,其功能如同第一種創建方法中的 action() 自定義函數。

下面程序,演示瞭如何通過繼承 Thread 類創建並啟動一個線程:

import threading
#創建子線程類,繼承自 Thread 類
class my_Thread(threading.Thread):
def __init__(self,add):
threading.Thread.__init__(self)
self.add = add
# 重寫run()方法
def run(self):
for arc in self.add:
#調用 getName() 方法獲取當前執行該程序的線程名
print(threading.current_thread().getName() +" "+ arc)
#定義為 run() 方法傳入的參數
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#創建子線程
mythread = my_Thread(my_tuple)
#啟動子線程
mythread.start()
#主線程執行此循環
for i in range(5):
print(threading.current_thread().getName())

程序執行結果為:

MainThreadThread-1 http://c.biancheng.net/python/
MainThreadThread-1 http://c.biancheng.net/shell/
MainThreadThread-1 http://c.biancheng.net/java/
MainThread
MainThread

此程序中,子線程 Thread-1 執行的是 run() 方法中的代碼,而 MainThread 執行的是主程序中的代碼,它們以快速輪換 CPU 的方式在執行。

Thread join()用法

如何通過 Thread 類創建並啟動一個線程,當時給讀者用如下的程序進行演示:

import threading
#定義線程要調用的方法,*add可接收多個以非關鍵字方式傳入的參數
def action(*add):
    for arc in add:
        #調用 getName() 方法獲取當前執行該程序的線程名
        print(threading.current_thread().getName() +" "+ arc)
#定義為線程方法傳入的參數
my_tuple = ("http://c.biancheng.net/python/",\
            "http://c.biancheng.net/shell/",\
            "http://c.biancheng.net/java/")
#創建線程
thread = threading.Thread(target = action,args =my_tuple)
#啟動線程
thread.start()
#主線程執行如下語句
for i in range(5):
    print(threading.current_thread().getName())

程序執行結果為(不唯一):

Thread-1 http://c.biancheng.net/python/MainThread
Thread-1 http://c.biancheng.net/shell/MainThread
Thread-1 http://c.biancheng.net/java/MainThread
MainThread
MainThread

可以看到,我們用 Thread 類創建瞭一個線程(線程名為 Thread-1),其任務是執行 action() 函數。同時,我們也給主線程 MainThread 安排瞭循環任務(第 16、17 行)。通過前面的學習我們知道,主線程 MainThread 和子線程 Thread-1 會輪流獲得 CPU 資源,因此該程序的輸出結果才會向上面顯示的這樣。
但是,如果我們想讓 Thread-1 子線程先執行,然後再讓 MainThread 執行第 16、17 行代碼,該如何實現呢?很簡單,通過調用線程對象的 join() 方法即可。
join() 方法的功能是在程序指定位置,優先讓該方法的調用者使用 CPU 資源。該方法的語法格式如下:thread.join( [timeout] )

其中,thread 為 Thread 類或其子類的實例化對象;timeout 參數作為可選參數,其功能是指定 thread 線程最多可以霸占 CPU 資源的時間(以秒為單位),如果省略,則默認直到 thread 執行結束(進入死亡狀態)才釋放 CPU 資源。

舉個例子,修改上面的代碼,如下所示:

import threading
#定義線程要調用的方法,*add可接收多個以非關鍵字方式傳入的參數
def action(*add):
for arc in add:
#調用 getName() 方法獲取當前執行該程序的線程名
print(threading.current_thread().getName() +" "+ arc)
#定義為線程方法傳入的參數
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#創建線程
thread = threading.Thread(target = action,args =my_tuple)
#啟動線程
thread.start()
#指定 thread 線程優先執行完畢
thread.join()
#主線程執行如下語句
for i in range(5):
print(threading.current_thread().getName())

程序執行結果為:

Thread-1 http://c.biancheng.net/python/
Thread-1 http://c.biancheng.net/shell/
Thread-1 http://c.biancheng.net/java/
MainThread
MainThread
MainThread
MainThread
MainThread

程序中第 16 行的位置,thread 線程調用瞭 join() 方法,並且沒有指定具體的 timeout 參數值。這意味著如果程序想繼續往下執行,必須先執行完 thread 線程。

到此這篇關於Python線程threading(Thread類)的文章就介紹到這瞭,更多相關Python線程 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: