Python多線程+線程守護(hù)+GIL鎖

今天開(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)解肆糕,望指教

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末般堆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诚啃,更是在濱河造成了極大的恐慌淮摔,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件始赎,死亡現(xiàn)場(chǎng)離奇詭異和橙,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)造垛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)魔招,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人五辽,你說(shuō)我怎么就攤上這事办斑。” “怎么了杆逗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵乡翅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我罪郊,道長(zhǎng)蠕蚜,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任排龄,我火速辦了婚禮波势,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘橄维。我一直安慰自己尺铣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布争舞。 她就那樣靜靜地躺著凛忿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪竞川。 梳的紋絲不亂的頭發(fā)上店溢,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天叁熔,我揣著相機(jī)與錄音,去河邊找鬼床牧。 笑死荣回,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戈咳。 我是一名探鬼主播心软,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼著蛙!你這毒婦竟也來(lái)了删铃?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤踏堡,失蹤者是張志新(化名)和其女友劉穎猎唁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體顷蟆,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诫隅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了慕的。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阎肝。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肮街,靈堂內(nèi)的尸體忽然破棺而出风题,到底是詐尸還是另有隱情,我是刑警寧澤嫉父,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布沛硅,位于F島的核電站,受9級(jí)特大地震影響绕辖,放射性物質(zhì)發(fā)生泄漏摇肌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一仪际、第九天 我趴在偏房一處隱蔽的房頂上張望围小。 院中可真熱鬧,春花似錦树碱、人聲如沸肯适。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)框舔。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刘绣,已是汗流浹背樱溉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纬凤,地道東北人福贞。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像移斩,于是被迫代替她去往敵國(guó)和親肚医。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 一文讀懂Python多線程 1向瓷、線程和進(jìn)程 計(jì)算機(jī)的核心是CPU,它承擔(dān)了所有的計(jì)算任務(wù)舰涌。它就像一座工廠猖任,時(shí)刻在運(yùn)...
    星丶雲(yún)閱讀 1,450評(píng)論 0 4
  • 環(huán)境 xubuntu anaconda pycharm python https://www.cnblogs.co...
    Ericoool閱讀 1,893評(píng)論 0 0
  • 線程 操作系統(tǒng)線程理論 線程概念的引入背景 進(jìn)程 之前我們已經(jīng)了解了操作系統(tǒng)中進(jìn)程的概念朱躺,程序并不能單獨(dú)運(yùn)行,只有...
    go以恒閱讀 1,635評(píng)論 0 6
  • 宋武帝劉裕去世后,謝晦鸡典、徐羨之源请、傅亮三人受命為顧命大臣,輔佐宋少帝劉義符彻况。但劉義符自幼嬌生慣養(yǎng)谁尸,登基后整日與宮人游...
    寒七琪閱讀 1,026評(píng)論 3 4
  • 如果落葉有痕跡 肯定是個(gè)婀娜的少女 用最曼妙的姿態(tài) 潛伏在人間。
    留子堯閱讀 286評(píng)論 2 4