Python線程threading(Thread類)
前言
幾乎所有的操作系統(tǒng)都支持同時(shí)運(yùn)行多個(gè)任務(wù),每個(gè)任務(wù)通常是一個(gè)程序,每一個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程,即進(jìn)程是應(yīng)用程序的執(zhí)行實(shí)例。現(xiàn)代的操作系統(tǒng)幾乎都支持多進(jìn)程并發(fā)執(zhí)行。注意,并發(fā)和并行是兩個(gè)概念,并行指在同一時(shí)刻有多條指令在多個(gè)處理器上同時(shí)執(zhí)行;并發(fā)是指在同一時(shí)刻只能有一條指令執(zhí)行,但多個(gè)進(jìn)程指令被快速輪換執(zhí)行,使得在宏觀上具有多個(gè)進(jìn)程同時(shí)執(zhí)行的效果。
例如,程序員一邊開(kāi)著開(kāi)發(fā)工具在寫(xiě)程序,一邊開(kāi)著參考手冊(cè)備查,同時(shí)還使用電腦播放音樂(lè)……除此之外,每臺(tái)電腦運(yùn)行時(shí)還有大量底層的支撐性程序在運(yùn)行……這些進(jìn)程看上去像是在同時(shí)工作。但事實(shí)的真相是,對(duì)于一個(gè) CPU 而言,在某個(gè)時(shí)間點(diǎn)它只能執(zhí)行一個(gè)程序。也就是說(shuō),只能運(yùn)行一個(gè)進(jìn)程,CPU 不斷地在這些進(jìn)程之間輪換執(zhí)行。那么,為什么用戶感覺(jué)不到任何中斷呢?這是因?yàn)橄鄬?duì)人的感覺(jué)來(lái)說(shuō),CPU 的執(zhí)行速度太快了(如果啟動(dòng)的程序足夠多,則用戶依然可以感覺(jué)到程序的運(yùn)行速度下降了)。所以,雖然 CPU 在多個(gè)進(jìn)程之間輪換執(zhí)行,但用戶感覺(jué)到好像有多個(gè)進(jìn)程在同時(shí)執(zhí)行。
線程是進(jìn)程的組成部分,一個(gè)進(jìn)程可以擁有多個(gè)線程。在多線程中,會(huì)有一個(gè)主線程來(lái)完成整個(gè)進(jìn)程從開(kāi)始到結(jié)束的全部操作,而其他的線程會(huì)在主線程的運(yùn)行過(guò)程中被創(chuàng)建或退出。當(dāng)進(jìn)程被初始化后,主線程就被創(chuàng)建了,對(duì)于絕大多數(shù)的應(yīng)用程序來(lái)說(shuō),通常僅要求有一個(gè)主線程,但也可以在進(jìn)程內(nèi)創(chuàng)建多個(gè)順序執(zhí)行流,這些順序執(zhí)行流就是線程。當(dāng)一個(gè)進(jìn)程里只有一個(gè)線程時(shí),叫作單線程。超過(guò)一個(gè)線程就叫作多線程。每個(gè)線程必須有自己的父進(jìn)程,且它可以擁有自己的堆棧、程序計(jì)數(shù)器和局部變量,但不擁有系統(tǒng)資源,因?yàn)?strong>它和父進(jìn)程的其他線程共享該進(jìn)程所擁有的全部資源。線程可以完成一定的任務(wù),可以與其他線程共享父進(jìn)程中的共享變量及部分環(huán)境,相互之間協(xié)同完成進(jìn)程所要完成的任務(wù)。多個(gè)線程共享父進(jìn)程里的全部資源,會(huì)使得編程更加方便,需要注意的是,要確保線程不會(huì)妨礙同一進(jìn)程中的其他線程。線程是獨(dú)立運(yùn)行的,它并不知道進(jìn)程中是否還有其他線程存在。線程的運(yùn)行是搶占式的,也就是說(shuō),當(dāng)前運(yùn)行的線程在任何時(shí)候都可能被掛起,以便另外一個(gè)線程可以運(yùn)行。多線程也是并發(fā)執(zhí)行的,即同一時(shí)刻,Python 主程序只允許有一個(gè)線程執(zhí)行,這和全局解釋器鎖有關(guān)系,后續(xù)會(huì)做詳細(xì)介紹。
一個(gè)線程可以創(chuàng)建和撤銷另一個(gè)線程,同一個(gè)進(jìn)程中的多個(gè)線程之間可以并發(fā)運(yùn)行。從邏輯的角度來(lái)看,多線程存在于一個(gè)應(yīng)用程序中,讓一個(gè)應(yīng)用程序可以有多個(gè)執(zhí)行部分同時(shí)執(zhí)行,但操作系統(tǒng)無(wú)須將多個(gè)線程看作多個(gè)獨(dú)立的應(yīng)用,對(duì)多線程實(shí)現(xiàn)調(diào)度和管理以及資源分配,線程的調(diào)度和管理由進(jìn)程本身負(fù)責(zé)完成。
簡(jiǎn)而言之,進(jìn)程和線程的關(guān)系是這樣的:操作系統(tǒng)可以同時(shí)執(zhí)行多個(gè)任務(wù),每一個(gè)任務(wù)就是一個(gè)進(jìn)程,進(jìn)程可以同時(shí)執(zhí)行多個(gè)任務(wù),每一個(gè)任務(wù)就是一個(gè)線程。
Python創(chuàng)建線程 threading
Python 中,有關(guān)線程開(kāi)發(fā)的部分被單獨(dú)封裝到了模塊中,和線程相關(guān)的模塊有以下 2 個(gè):
???_thread???:是 Python 3 以前版本中 thread 模塊的重命名,此模塊僅提供了低級(jí)別的、原始的線程支持,以及一個(gè)簡(jiǎn)單的鎖。功能比較有限。正如它的名字所暗示的(以 _ 開(kāi)頭),一般不建議使用 thread 模塊;
???threading??:Python 3 之后的線程模塊,提供了功能豐富的多線程支持,推薦使用。
本節(jié)就以 threading 模塊為例進(jìn)行講解。Python 主要通過(guò)兩種方式來(lái)創(chuàng)建線程:
- 使用 threading 模塊中 Thread 類的構(gòu)造器創(chuàng)建線程。即直接對(duì)類 threading.Thread 進(jìn)行實(shí)例化創(chuàng)建線程,并調(diào)用實(shí)例化對(duì)象的 start() 方法啟動(dòng)線程。
- 繼承 threading 模塊中的 Thread 類創(chuàng)建線程類。即用 threading.Thread 派生出一個(gè)新的子類,將新建類實(shí)例化創(chuàng)建線程,并調(diào)用其 start() 方法啟動(dòng)線程。
調(diào)用Thread類的構(gòu)造器創(chuàng)建線程
Thread 類提供了如下的 ??__init__()???構(gòu)造器,可以用來(lái)創(chuàng)建線程:??__init__(self, group=None, target=None, name=None, args=(), kwargs=None, *,daemon=None)?? 此構(gòu)造方法中,以上所有參數(shù)都是可選參數(shù),即可以使用,也可以忽略。其中各個(gè)參數(shù)的含義如下:
- group:指定所創(chuàng)建的線程隸屬于哪個(gè)線程組(此參數(shù)尚未實(shí)現(xiàn),無(wú)需調(diào)用);
- target:指定所創(chuàng)建的線程要調(diào)度的目標(biāo)方法(最常用);
- args:以元組的方式,為 target 指定的方法傳遞參數(shù);
- kwargs:以字典的方式,為 target 指定的方法傳遞參數(shù);
- daemon:指定所創(chuàng)建的線程是否為后代線程。
這些參數(shù),初學(xué)者只需記住 target、args、kwargs 這 3 個(gè)參數(shù)的功能即可。
下面程序演示了如何使用 Thread 類的構(gòu)造方法創(chuàng)建一個(gè)線程:
import threading
#定義線程要調(diào)用的方法,*add可接收多個(gè)以非關(guān)鍵字方式傳入的參數(shù)
def action(*add):
for arc in add:
#調(diào)用 getName() 方法獲取當(dāng)前執(zhí)行該程序的線程名
print(threading.current_thread().getName() +" "+ arc)
#定義為線程方法傳入的參數(shù)
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#創(chuàng)建線程
thread = threading.Thread(target = action,args =my_tuple)有關(guān) Thread 類提供的和線程有關(guān)的方法,可閱讀? ?Python Thread手冊(cè)???,由于不是本節(jié)重點(diǎn),這里不再進(jìn)行詳細(xì)介紹。
由此就創(chuàng)建好了一個(gè)線程。但是線程需要手動(dòng)啟動(dòng)才能運(yùn)行,threading 模塊提供了 start() 方法用來(lái)啟動(dòng)線程。因此在上面程序的基礎(chǔ)上,添加如下語(yǔ)句:thread.start()
再次執(zhí)行程序,
其輸出結(jié)果為:
Thread-1 http://c.biancheng.net/python/
Thread-1 http://c.biancheng.net/shell/
Thread-1 http://c.biancheng.net/java/
可以看到,新創(chuàng)建的 thread 線程(線程名為 Thread-1)執(zhí)行了 action() 函數(shù)。
默認(rèn)情況下,主線程的名字為 MainThread,用戶啟動(dòng)的多個(gè)線程的名字依次為 Thread-1、Thread-2、Thread-3、…、Thread-n 等。
為了使 thread 線程的作用更加明顯,可以繼續(xù)在上面程序的基礎(chǔ)上添加如下代碼,讓主線程和新創(chuàng)建線程同時(shí)工作:
for i in range(5): print(threading.current_thread().getName())
再次執(zhí)行程序,其輸出結(jié)果為:
MainThreadThread-1 http://c.biancheng.net/python/
MainThreadThread-1 http://c.biancheng.net/shell/
MainThreadThread-1 http://c.biancheng.net/java/
MainThread
MainThread
可以看到,當(dāng)前程序中有 2 個(gè)線程,分別為主線程 MainThread 和子線程 Thread-1,它們以并發(fā)方式執(zhí)行,即 Thread-1 執(zhí)行一段時(shí)間,然后 MainThread 執(zhí)行一段時(shí)間。通過(guò)輪流獲得 CPU 執(zhí)行一段時(shí)間的方式,程序的執(zhí)行在多個(gè)線程之間切換,從而給用戶一種錯(cuò)覺(jué),即多個(gè)線程似乎同時(shí)在執(zhí)行。
如果程序中不顯式創(chuàng)建任何線程,則所有程序的執(zhí)行,都將由主線程 MainThread 完成,程序就只能按照順序依次執(zhí)行。
繼承Thread類創(chuàng)建線程類
通過(guò)繼承 Thread 類,我們可以自定義一個(gè)線程類,從而實(shí)例化該類對(duì)象,獲得子線程。需要注意的是,在創(chuàng)建 Thread 類的子類時(shí),必須重寫(xiě)從父類繼承得到的 run() 方法。因?yàn)樵摲椒礊橐獎(jiǎng)?chuàng)建的子線程執(zhí)行的方法,其功能如同第一種創(chuàng)建方法中的 action() 自定義函數(shù)。
下面程序,演示了如何通過(guò)繼承 Thread 類創(chuàng)建并啟動(dòng)一個(gè)線程:
import threading
#創(chuàng)建子線程類,繼承自 Thread 類
class my_Thread(threading.Thread):
def __init__(self,add):
threading.Thread.__init__(self)
self.add = add
# 重寫(xiě)run()方法
def run(self):
for arc in self.add:
#調(diào)用 getName() 方法獲取當(dāng)前執(zhí)行該程序的線程名
print(threading.current_thread().getName() +" "+ arc)
#定義為 run() 方法傳入的參數(shù)
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#創(chuàng)建子線程
mythread = my_Thread(my_tuple)
#啟動(dòng)子線程
mythread.start()
#主線程執(zhí)行此循環(huán)
for i in range(5):
print(threading.current_thread().getName())程序執(zhí)行結(jié)果為:
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 執(zhí)行的是 run() 方法中的代碼,而 MainThread 執(zhí)行的是主程序中的代碼,它們以快速輪換 CPU 的方式在執(zhí)行。
Thread join()用法
如何通過(guò) Thread 類創(chuàng)建并啟動(dòng)一個(gè)線程,當(dāng)時(shí)給讀者用如下的程序進(jìn)行演示:
import threading
#定義線程要調(diào)用的方法,*add可接收多個(gè)以非關(guān)鍵字方式傳入的參數(shù)
def action(*add):
for arc in add:
#調(diào)用 getName() 方法獲取當(dāng)前執(zhí)行該程序的線程名
print(threading.current_thread().getName() +" "+ arc)
#定義為線程方法傳入的參數(shù)
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#創(chuàng)建線程
thread = threading.Thread(target = action,args =my_tuple)
#啟動(dòng)線程
thread.start()
#主線程執(zhí)行如下語(yǔ)句
for i in range(5):
print(threading.current_thread().getName())程序執(zhí)行結(jié)果為(不唯一):
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 類創(chuàng)建了一個(gè)線程(線程名為 Thread-1),其任務(wù)是執(zhí)行 action() 函數(shù)。同時(shí),我們也給主線程 MainThread 安排了循環(huán)任務(wù)(第 16、17 行)。通過(guò)前面的學(xué)習(xí)我們知道,主線程 MainThread 和子線程 Thread-1 會(huì)輪流獲得 CPU 資源,因此該程序的輸出結(jié)果才會(huì)向上面顯示的這樣。
但是,如果我們想讓 Thread-1 子線程先執(zhí)行,然后再讓 MainThread 執(zhí)行第 16、17 行代碼,該如何實(shí)現(xiàn)呢?很簡(jiǎn)單,通過(guò)調(diào)用線程對(duì)象的 join() 方法即可。
join() 方法的功能是在程序指定位置,優(yōu)先讓該方法的調(diào)用者使用 CPU 資源。該方法的語(yǔ)法格式如下:thread.join( [timeout] )
其中,thread 為 Thread 類或其子類的實(shí)例化對(duì)象;timeout 參數(shù)作為可選參數(shù),其功能是指定 thread 線程最多可以霸占 CPU 資源的時(shí)間(以秒為單位),如果省略,則默認(rèn)直到 thread 執(zhí)行結(jié)束(進(jìn)入死亡狀態(tài))才釋放 CPU 資源。
舉個(gè)例子,修改上面的代碼,如下所示:
import threading
#定義線程要調(diào)用的方法,*add可接收多個(gè)以非關(guān)鍵字方式傳入的參數(shù)
def action(*add):
for arc in add:
#調(diào)用 getName() 方法獲取當(dāng)前執(zhí)行該程序的線程名
print(threading.current_thread().getName() +" "+ arc)
#定義為線程方法傳入的參數(shù)
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#創(chuàng)建線程
thread = threading.Thread(target = action,args =my_tuple)
#啟動(dòng)線程
thread.start()
#指定 thread 線程優(yōu)先執(zhí)行完畢
thread.join()
#主線程執(zhí)行如下語(yǔ)句
for i in range(5):
print(threading.current_thread().getName())程序執(zhí)行結(jié)果為:
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 線程調(diào)用了 join() 方法,并且沒(méi)有指定具體的 timeout 參數(shù)值。這意味著如果程序想繼續(xù)往下執(zhí)行,必須先執(zhí)行完 thread 線程。
到此這篇關(guān)于Python線程threading(Thread類)的文章就介紹到這了,更多相關(guān)Python線程 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實(shí)例講解Python中浮點(diǎn)型的基本內(nèi)容
在本文里小編給大家整理了關(guān)于Python中浮點(diǎn)型的基本知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們學(xué)習(xí)下。2019-02-02
Python實(shí)現(xiàn)字典按照value進(jìn)行排序的方法分析
這篇文章主要介紹了Python實(shí)現(xiàn)字典按照value進(jìn)行排序的方法,結(jié)合實(shí)例形式分析了Python字典按照value進(jìn)行排序的相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
Python實(shí)現(xiàn)數(shù)據(jù)的序列化操作詳解
在日常開(kāi)發(fā)中,對(duì)數(shù)據(jù)進(jìn)行序列化和反序列化是常見(jiàn)的數(shù)據(jù)操作,Python提供了兩個(gè)模塊方便開(kāi)發(fā)者實(shí)現(xiàn)數(shù)據(jù)的序列化操作,即?json?模塊和?pickle?模塊。本文就為大家詳細(xì)講解這兩個(gè)模塊的使用,需要的可以參考一下2022-07-07
全面解析python當(dāng)前路徑和導(dǎo)包路徑問(wèn)題
這篇文章主要為大家介紹了python當(dāng)前路徑和導(dǎo)包路徑問(wèn)題的全面解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
使用matplotlib動(dòng)態(tài)刷新指定曲線實(shí)例
這篇文章主要介紹了使用matplotlib動(dòng)態(tài)刷新指定曲線實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04
基于ID3決策樹(shù)算法的實(shí)現(xiàn)(Python版)
下面小編就為大家?guī)?lái)一篇基于ID3決策樹(shù)算法的實(shí)現(xiàn)(Python版)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
Python接入MySQL實(shí)現(xiàn)增刪改查的實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于Python接入MySQL實(shí)現(xiàn)增刪改查的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
時(shí)間序列分析之ARIMA模型預(yù)測(cè)餐廳銷量
這篇文章主要介紹了時(shí)間序列分析之ARIMA模型預(yù)測(cè)餐廳銷量,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11

