今天開(kāi)始打算開(kāi)一個(gè)新系列诗宣,就是python的多線程和多進(jìn)程實(shí)現(xiàn)掷豺,這部分可能有些新手還是比較模糊的囱修,都知道python中的多線程是假的赎瑰,但是又不知道怎么回事,首先我們看一個(gè)例子來(lái)看看python多線程的實(shí)現(xiàn)破镰。
import threading
import time
def say(name):
? ? ? ? print('你好%s at %s' %(name,time.ctime()))
? ? ? ? time.sleep(2)
? ? ? ? print("結(jié)束%s at %s" %(name,time.ctime()))
def listen(name):
? ? print('你好%s at %s' % (name,time.ctime()))
? ? time.sleep(4)
? ? print("結(jié)束%s at %s" % (name,time.ctime()))
if __name__ == '__main__':
? ? t1 = threading.Thread(target=say,args=('tony',))? #Thread是一個(gè)類(lèi)餐曼,實(shí)例化產(chǎn)生t1對(duì)象,這里就是創(chuàng)建了一個(gè)線程對(duì)象t1
? ? t1.start() #線程執(zhí)行
? ? t2 = threading.Thread(target=listen, args=('simon',)) #這里就是創(chuàng)建了一個(gè)線程對(duì)象t2
? ? t2.start()
? ? print("程序結(jié)束=====================")
結(jié)果:
你好tony at Thu Apr 25 16:46:22 2019
你好simon at Thu Apr 25 16:46:22 2019
程序結(jié)束=====================
結(jié)束tony at Thu Apr 25 16:46:24 2019
結(jié)束simon at Thu Apr 25 16:46:26 2019
Process finished with exit code 0
python的多線程是通過(guò)threading模塊的Thread類(lèi)來(lái)實(shí)現(xiàn)的鲜漩。
創(chuàng)建線程對(duì)象
t1 = threading.Thread(target=say,args=('tony',))? #Thread是一個(gè)類(lèi)源譬,實(shí)例化產(chǎn)生t1對(duì)象,這里就是創(chuàng)建了一個(gè)線程對(duì)象t1
啟動(dòng)線程
t1.start() #線程執(zhí)行
下面我們分析下上面代碼的結(jié)果:
你好tony at Thu Apr 25 16:46:22 2019? --t1線程執(zhí)行
你好simon at Thu Apr 25 16:46:22 2019 --t2線程執(zhí)行
程序結(jié)束===================== --主線程執(zhí)行
結(jié)束tony at Thu Apr 25 16:46:24 2019 --sleep之后孕似,t1線程執(zhí)行
結(jié)束simon at Thu Apr 25 16:46:26 2019 --sleep之后踩娘,t2線程執(zhí)行
Process finished with exit code 0 --主線程結(jié)束
我們可以看到主線程的print并不是等t1,t2線程都執(zhí)行完畢之后才打印的,這是因?yàn)橹骶€程和t1,t2 線程是同時(shí)跑的喉祭。但是主進(jìn)程要等非守護(hù)子線程結(jié)束之后养渴,主線程才會(huì)退出。
上面其實(shí)就是python多線程的最簡(jiǎn)單用法泛烙,但是可能有人會(huì)和我有一樣的需求理卑,一般開(kāi)發(fā)中,我們需要主線程的print打印是在最后面的蔽氨,表明所有流程都結(jié)束了藐唠,也就是主線程結(jié)束了帆疟。這里就引入了一個(gè)join的概念。
import threading
import time
def say(name):
? ? ? ? print('你好%s at %s' %(name,time.ctime()))
? ? ? ? time.sleep(2)
? ? ? ? print("結(jié)束%s at %s" %(name,time.ctime()))
def listen(name):
? ? print('你好%s at %s' % (name,time.ctime()))
? ? time.sleep(4)
? ? print("結(jié)束%s at %s" % (name,time.ctime()))
if __name__ == '__main__':
? ? t1 = threading.Thread(target=say,args=('tony',))? #Thread是一個(gè)類(lèi)宇立,實(shí)例化產(chǎn)生t1對(duì)象踪宠,這里就是創(chuàng)建了一個(gè)線程對(duì)象t1
? ? t1.start() #線程執(zhí)行
? ? t2 = threading.Thread(target=listen, args=('simon',)) #這里就是創(chuàng)建了一個(gè)線程對(duì)象t2
? ? t2.start()
? ? t1.join() #join等t1子線程結(jié)束,主線程打印并且結(jié)束
? ? t2.join() #join等t2子線程結(jié)束泄伪,主線程打印并且結(jié)束
? ? print("程序結(jié)束=====================")
結(jié)果:
你好tony at Thu Apr 25 16:57:32 2019
你好simon at Thu Apr 25 16:57:32 2019
結(jié)束tony at Thu Apr 25 16:57:34 2019
結(jié)束simon at Thu Apr 25 16:57:36 2019
程序結(jié)束=====================
上面代碼中加入join方法后實(shí)現(xiàn)了殴蓬,我們上面所想要的結(jié)果,主線程print最后執(zhí)行蟋滴,并且主線程退出染厅,注意主線程執(zhí)行了打印操作和主線程結(jié)束不是一個(gè)概念,如果子線程不加join津函,則主線程也會(huì)執(zhí)行打印肖粮,但是主線程不會(huì)結(jié)束,還是需要待非守護(hù)子線程結(jié)束之后尔苦,主線程才結(jié)束涩馆。
上面的情況,主進(jìn)程都需要等待非守護(hù)子線程結(jié)束之后允坚,主線程才結(jié)束魂那。那我們是不是注意到一點(diǎn),我說(shuō)的是“非守護(hù)子線程”稠项,那什么是非守護(hù)子線程涯雅?默認(rèn)的子線程都是主線程的非守護(hù)子線程,但是有時(shí)候我們有需求展运,當(dāng)主進(jìn)程結(jié)束活逆,不管子線程有沒(méi)有結(jié)束,子線程都要跟隨主線程一起退出拗胜,這時(shí)候我們引入一個(gè)“守護(hù)線程”的概念蔗候。
如果某個(gè)子線程設(shè)置為守護(hù)線程,主線程其實(shí)就不用管這個(gè)子線程了埂软,當(dāng)所有其他非守護(hù)線程結(jié)束锈遥,主線程就會(huì)退出,而守護(hù)線程將和主線程一起退出勘畔,守護(hù)主線程迷殿,這就是守護(hù)線程的意思
看看具體代碼,我們這里分2種情況來(lái)討論守護(hù)線程咖杂,加深大家的理解,
還有一點(diǎn)蚊夫,這個(gè)方法一定要設(shè)置在start方法前面
1.設(shè)置t1線程為守護(hù)線程诉字,看看執(zhí)行結(jié)果
import threading
import time
def say(name):
? ? ? ? print('你好%s at %s' %(name,time.ctime()))
? ? ? ? time.sleep(2)
? ? ? ? print("結(jié)束%s at %s" %(name,time.ctime()))
def listen(name):
? ? print('你好%s at %s' % (name,time.ctime()))
? ? time.sleep(4)
? ? print("結(jié)束%s at %s" % (name,time.ctime()))
if __name__ == '__main__':
? ? t1 = threading.Thread(target=say,args=('tony',))? #Thread是一個(gè)類(lèi),實(shí)例化產(chǎn)生t1對(duì)象,這里就是創(chuàng)建了一個(gè)線程對(duì)象t1
? ? t1.setDaemon(True)
? ? t1.start() #線程執(zhí)行
? ? t2 = threading.Thread(target=listen, args=('simon',)) #這里就是創(chuàng)建了一個(gè)線程對(duì)象t2
? ? t2.start()
? ? print("程序結(jié)束=====================")
結(jié)果:
你好tony at Thu Apr 25 17:11:41 2019
你好simon at Thu Apr 25 17:11:41 2019
程序結(jié)束=====================
結(jié)束tony at Thu Apr 25 17:11:43 2019
結(jié)束simon at Thu Apr 25 17:11:45 2019
注意執(zhí)行順序壤圃,
這里如果設(shè)置t1為Daemon陵霉,那么主線程就不管t1的運(yùn)行狀態(tài),只管等待t2結(jié)束伍绳, t2結(jié)束主線程就結(jié)束了
因?yàn)閠2的時(shí)間4秒踊挠,t1的時(shí)間2秒,主線程在等待t2線程結(jié)束的過(guò)程中冲杀,t1線程自己結(jié)束了效床,所以結(jié)果是:
你好tony at Thu Apr 25 14:11:54 2019
你好simon at Thu Apr 25 14:11:54 2019程序結(jié)束===============
結(jié)束tony at Thu Apr 25 14:11:56 2019? (也會(huì)打印,因?yàn)橹骶€程在等待t2線程結(jié)束的過(guò)程中权谁, t1線程自己結(jié)束了)
結(jié)束simon at Thu Apr 25 14:11:58 2019
2.設(shè)置t2為守護(hù)線程
import threading
import time
def say(name):
? ? ? ? print('你好%s at %s' %(name,time.ctime()))
? ? ? ? time.sleep(2)
? ? ? ? print("結(jié)束%s at %s" %(name,time.ctime()))
def listen(name):
? ? print('你好%s at %s' % (name,time.ctime()))
? ? time.sleep(4)
? ? print("結(jié)束%s at %s" % (name,time.ctime()))
if __name__ == '__main__':
? ? t1 = threading.Thread(target=say,args=('tony',))? #Thread是一個(gè)類(lèi)剩檀,實(shí)例化產(chǎn)生t1對(duì)象,這里就是創(chuàng)建了一個(gè)線程對(duì)象t1
? ? t1.start() #線程執(zhí)行
? ? t2 = threading.Thread(target=listen, args=('simon',)) #這里就是創(chuàng)建了一個(gè)線程對(duì)象t2
? ? t2.setDaemon(True)
? ? t2.start()
? ? print("程序結(jié)束=====================")
結(jié)果:
你好tony at Thu Apr 25 17:15:36 2019
你好simon at Thu Apr 25 17:15:36 2019
程序結(jié)束=====================
結(jié)束tony at Thu Apr 25 17:15:38 2019
注意執(zhí)行順序:
這里如果設(shè)置t2為Daemon旺芽,那么主線程就不管t2的運(yùn)行狀態(tài)沪猴,只管等待t1結(jié)束, t1結(jié)束主線程就結(jié)束了
因?yàn)閠2的時(shí)間4秒采章,t1的時(shí)間2秒运嗜, 主線程在等待t1線程結(jié)束的過(guò)程中, t2線程自己結(jié)束不了悯舟,所以結(jié)果是:
你好tony at Thu Apr 25 14:14:23 2019
你好simon at Thu Apr 25 14:14:23 2019
程序結(jié)束 == == == == == == == == == == =
結(jié)束tony at Thu Apr 25 14:14:25 2019
結(jié)束simon at Thu Apr 25 14:11:58 2019 不會(huì)打印担租,因?yàn)橹骶€程在等待t1線程結(jié)束的過(guò)程中瞧甩, t2線程自己結(jié)束不了苍柏,t2的時(shí)間4秒饥脑,t1的時(shí)間2秒
不知道大家有沒(méi)有弄清楚上面python多線程的實(shí)現(xiàn)方式以及join,守護(hù)線程的用法闲询。
主要方法:
join():在子線程完成運(yùn)行之前呕缭,這個(gè)子線程的父線程將一直被阻塞槽奕。
setDaemon(True):
將線程聲明為守護(hù)線程稻励,必須在start() 方法調(diào)用之前設(shè)置禀梳, 如果不設(shè)置為守護(hù)線程程序會(huì)被無(wú)限掛起承璃。這個(gè)方法基本和join是相反的利耍。
當(dāng)我們?cè)诔绦蜻\(yùn)行中,執(zhí)行一個(gè)主線程盔粹,如果主線程又創(chuàng)建一個(gè)子線程隘梨,主線程和子線程 就分兵兩路,分別運(yùn)行舷嗡,那么當(dāng)主線程完成
想退出時(shí)轴猎,會(huì)檢驗(yàn)子線程是否完成。如 果子線程未完成进萄,則主線程會(huì)等待子線程完成后再退出捻脖。但是有時(shí)候我們需要的是 只要主線程
? ? 完成了锐峭,不管子線程是否完成,都要和主線程一起退出可婶,這時(shí)就可以 用setDaemon方法啦
其他方法:
run():? 線程被cpu調(diào)度后自動(dòng)執(zhí)行線程對(duì)象的run方法
start():啟動(dòng)線程活動(dòng)沿癞。
isAlive(): 返回線程是否活動(dòng)的。
getName(): 返回線程名矛渴。
setName(): 設(shè)置線程名椎扬。
threading模塊提供的一些方法:
threading.currentThread(): 返回當(dāng)前的線程變量。
threading.enumerate(): 返回一個(gè)包含正在運(yùn)行的線程的list具温。正在運(yùn)行指線程啟動(dòng)后蚕涤、結(jié)束前,不包括啟動(dòng)前和終止后的線程桂躏。
threading.activeCount():返回正在運(yùn)行的線程數(shù)量钻趋,與len(threading.enumerate())有相同的結(jié)果。
上面的例子中我們注意到兩如果個(gè)任務(wù)如果順序執(zhí)行要6s結(jié)束剂习,如果是多線程執(zhí)行4S結(jié)束蛮位,性能是有所提升的,但是我們要知道這里的性能提升實(shí)際上是由于cpu并發(fā)實(shí)現(xiàn)性能提升鳞绕,也就是cpu線程切換(多道技術(shù))帶來(lái)的失仁,而并不是真正的多cpu并行執(zhí)行。
上面提到了并行和并發(fā)们何,那這兩者有什么區(qū)別呢萄焦?
并發(fā):是指一個(gè)系統(tǒng)具有處理多個(gè)任務(wù)的能力(cpu切換,多道技術(shù))
并行:是指一個(gè)系統(tǒng)具有同時(shí)處理多個(gè)任務(wù)的能力(cpu同時(shí)處理多個(gè)任務(wù))
并行是并發(fā)的一種情況冤竹,子集
那為什么python在多線程中為什么不能實(shí)現(xiàn)真正的并行操作呢拂封?就是在多cpu中執(zhí)行不同的線程(我們知道JAVA中多個(gè)線程可以在不同的cpu中,實(shí)現(xiàn)并行運(yùn)行)這就要提到python中大名鼎鼎GIL鹦蠕,那什么是GIL?
GIL:全局解釋器鎖? 無(wú)論你啟多少個(gè)線程冒签,你有多少個(gè)cpu, Python在執(zhí)行的時(shí)候只會(huì)的在同一時(shí)刻只允許一個(gè)線程(線程之間有競(jìng)爭(zhēng))拿到GIL在一個(gè)cpu上運(yùn)行,當(dāng)線程遇到IO等待或到達(dá)者輪詢時(shí)間的時(shí)候钟病,cpu會(huì)做切換萧恕,把cpu的時(shí)間片讓給其他線程執(zhí)行,cpu切換需要消耗時(shí)間和資源肠阱,所以計(jì)算密集型的功能(比如加減乘除)不適合多線程票唆,因?yàn)閏pu線程切換太多,IO密集型比較適合多線程屹徘。
任務(wù):
IO密集型(各個(gè)線程都會(huì)都各種的等待走趋,如果有等待,線程切換是比較適合的)噪伊,也可以采用可以用多進(jìn)程+協(xié)程
計(jì)算密集型(線程在計(jì)算時(shí)沒(méi)有等待吆视,這時(shí)候去切換典挑,就是無(wú)用的切換),python不太適合開(kāi)發(fā)這類(lèi)功能
我們前面舉得例子里面模擬了sleep操作啦吧,其實(shí)就是相當(dāng)于遇到IO,這種場(chǎng)景用多線程是可以增加性能的拙寡,但是如果我們用多線程來(lái)計(jì)算數(shù)據(jù)的計(jì)算授滓,性能反而會(huì)降低。
下面是GIL的一段原生解釋?zhuān)?/p>
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once.
This lock is necessary mainly because CPython’s memory management is not thread-safe.
(However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
個(gè)人見(jiàn)解肆糕,望指教