到目前為止色冀,前面的章節(jié)文章都是單線程運(yùn)行在旱。就像一個(gè)人工作偷线,必須一件事結(jié)束磨确,再開(kāi)始另一件事情,這就是所謂的單線程
多線程就是雇傭多個(gè)人声邦,一起做同一件事或者將不同的事分給不同的人去做乏奥,可以大幅度的提交效率
先來(lái)看下多線程需要的庫(kù):Python內(nèi)置的線程庫(kù)threading
線程的運(yùn)行,通過(guò)threading亥曹,初始化得到一個(gè)實(shí)例邓了,然后給他一個(gè)可以執(zhí)行的函數(shù),讓他執(zhí)行就不用管了媳瞪,就像你在電腦上把音樂(lè)軟件打開(kāi)骗炉,然后隱藏到后臺(tái),它還是會(huì)繼續(xù)工作蛇受,然后你繼續(xù)做你的事情是一樣的意思句葵。
能開(kāi)一個(gè)線程,就能開(kāi)多個(gè)線程.....
在代碼中初始化一個(gè)線程龙巨,并給他函數(shù)讓他執(zhí)行笼呆,他跑到后臺(tái)去執(zhí)行了,然后代碼可以繼續(xù)往下工作旨别,先上一段代碼理解下:
import threading, time
def loop():
print("loop start run")
time.sleep(5)
print("loop exit")
t = threading.Thread(target=loop, name="LoopThread")
t.start()
print("代碼運(yùn)行結(jié)束")
解釋一下代碼:
- 首先導(dǎo)入兩個(gè)庫(kù),一個(gè)是線程threading汗茄,另一個(gè)是time庫(kù)【主要是sleep函數(shù)】
- 然后在代碼中秸弛,定義了loop函數(shù),執(zhí)行操作有打印loop start run,接著睡眠5秒鐘递览,接著打印loop exit
- 再是初始化一個(gè)線程叼屠,目標(biāo)函數(shù)是loop,給線程賦一個(gè)名字LoopThread
- 啟動(dòng)線程绞铃,然后不管了镜雨,腳本代碼打印代碼運(yùn)行結(jié)束之后退出
先來(lái)看結(jié)果圖:
從結(jié)果圖來(lái)看,整體運(yùn)行了5秒儿捧,但是代碼運(yùn)行結(jié)束在loop exit的前面荚坞,也就是說(shuō)線程的運(yùn)行情況和當(dāng)前代碼執(zhí)行順序無(wú)關(guān),它只管運(yùn)行它的菲盾。
就像是我有個(gè)任務(wù)——執(zhí)行l(wèi)oop函數(shù)颓影,我嫌函數(shù)無(wú)聊不想執(zhí)行,我就找一個(gè)人來(lái)把我這個(gè)任務(wù)拿去執(zhí)行懒鉴,然后我做我的事诡挂。
理論上兩個(gè)時(shí)間是重合的,時(shí)間會(huì)剪短临谱,但是這里是看不出來(lái)的璃俗,因?yàn)榇a執(zhí)行的太快,時(shí)間和5秒相比毫無(wú)意義悉默。
接下來(lái)定義兩個(gè)旧找,來(lái)測(cè)試一下時(shí)間問(wèn)題:
import threading, time
def loop():
print("loop start run")
time.sleep(5)
print("loop exit")
t1 = threading.Thread(target=loop, name="LoopThread-1")
t2 = threading.Thread(target=loop, name="LoopThread-2")
t1.start()
t2.start()
time.sleep(3)
print("代碼運(yùn)行結(jié)束")
這個(gè)示例是基于上一個(gè)示例的,現(xiàn)在創(chuàng)建兩個(gè)線程麦牺,都在運(yùn)行钮蛛,理論上睡眠時(shí)間是5+5+3=13秒的,來(lái)看下具體執(zhí)行情況:
還是短短的5.1秒剖膳,遠(yuǎn)小于13秒魏颓。
簡(jiǎn)單說(shuō)下為什么:t1睡眠5秒的時(shí)間和t2睡眠5秒是重合的,還有代碼睡眠3秒是在5秒的時(shí)間范圍內(nèi)的吱晒,所以最后的運(yùn)行時(shí)間也就是程序開(kāi)始甸饱,到程序全部結(jié)束的時(shí)間。
明白了線程的運(yùn)行仑濒,然后繼續(xù)了解下線程的其他知識(shí)點(diǎn)
知識(shí)點(diǎn)一:父線程和子線程
父線程和子線程是相對(duì)的叹话。就拿剛才的示例來(lái)講,代碼執(zhí)行是單線程墩瞳,代碼中創(chuàng)建了t1和t2驼壶,對(duì)于代碼的單線程來(lái)講,t1和t2是它的子線程喉酌,因?yàn)樗麄兪潜淮a的單線程創(chuàng)建出來(lái)的热凹。
知識(shí)點(diǎn)二:守護(hù)線程
守護(hù)線程和普通線程基本沒(méi)什么區(qū)別泵喘,但是有一點(diǎn)特殊,即守護(hù)線程還沒(méi)執(zhí)行完般妙,主線程退出纪铺,守護(hù)線程會(huì)強(qiáng)制退出;但是主線程執(zhí)行結(jié)束碟渺,普通線程沒(méi)結(jié)束鲜锚,主線程會(huì)等待普通線程結(jié)束再退出
上一個(gè)示例代碼和運(yùn)行結(jié)果圖:
import threading, time
def loop():
print("loop start run")
time.sleep(5)
print("loop exit")
t = threading.Thread(target=loop, name="LoopThread")
t.setDaemon(True)
t.start()
print("代碼運(yùn)行結(jié)束")
子線程還沒(méi)結(jié)束,主線程已經(jīng)結(jié)束了苫拍,強(qiáng)制退出芜繁,運(yùn)行時(shí)間僅0.2秒。
對(duì)比本文第一個(gè)示例【非守護(hù)線程】怯疤,總執(zhí)行時(shí)間有5.1秒呢
知識(shí)點(diǎn)三:線程和進(jìn)程
線程:線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位浆洗。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位集峦。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流伏社,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)塔淤。一個(gè)線程是一個(gè)execution context(執(zhí)行上下文)摘昌,即一個(gè)cpu執(zhí)行時(shí)所需要的一串指令。
進(jìn)程:一個(gè)程序的執(zhí)行實(shí)例就是一個(gè)進(jìn)程高蜂。每一個(gè)進(jìn)程提供執(zhí)行程序所需的所有資源聪黎。(進(jìn)程本質(zhì)上是資源的集合)
一個(gè)進(jìn)程有一個(gè)虛擬的地址空間、可執(zhí)行的代碼备恤、操作系統(tǒng)的接口稿饰、安全的上下文(記錄啟動(dòng)該進(jìn)程的用戶和權(quán)限等等)、唯一的進(jìn)程ID露泊、環(huán)境變量喉镰、優(yōu)先級(jí)類、最小和最大的工作空間(內(nèi)存空間)惭笑,還要有至少一個(gè)線程侣姆。
知識(shí)點(diǎn)四:Python的線程是雞肋?
用C沉噩、C++來(lái)寫死循環(huán)捺宗,直接可以把全部核心跑滿,4核就跑到400%川蒙,8核就跑到800%蚜厉,但是Python是不行的。
因?yàn)镻ython的線程雖然是真正的線程派歌,但解釋器執(zhí)行代碼時(shí)弯囊,有一個(gè)GIL鎖:Global Interpreter Lock痰哨,任何Python線程執(zhí)行前胶果,必須先獲得GIL鎖匾嘱,然后,每執(zhí)行100條字節(jié)碼早抠,解釋器就自動(dòng)釋放GIL鎖霎烙,讓別的線程有機(jī)會(huì)執(zhí)行。這個(gè)GIL全局鎖實(shí)際上把所有線程的執(zhí)行代碼都給上了鎖蕊连,所以悬垃,多線程在Python中只能交替執(zhí)行,即使100個(gè)線程跑在100核CPU上甘苍,也只能用到1個(gè)核尝蠕。
Python3更多教程——傳送門