python多线程与GIL
目錄
1.GIL? ? ??
1.1 為什么要有GIL
1.2 GIL的運(yùn)作方式
1.3?GIL帶來的問題
2.多線線程
2.1 線程的調(diào)度和啟動(dòng)
3.線程構(gòu)造與使用
3.1調(diào)用Thread類構(gòu)造器創(chuàng)建線程
3.2繼承Thread類創(chuàng)建線程類
在學(xué)習(xí)python多線程過程,學(xué)習(xí)一些很不錯(cuò)的文章和教程,這里進(jìn)行摘抄并總結(jié)歸納一下,便于加深自己的理解
1.GIL? ? ??
? ? GIL的全稱是全局解釋器鎖(Global Interpreter Lock),GIL并不是Python的特性,它是在實(shí)現(xiàn)Python解析器(CPython)時(shí)所引入的一個(gè)概念。就好比C++,可以用不同的編譯器來編譯成可執(zhí)行代碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也一樣,同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執(zhí)行環(huán)境來執(zhí)行。像其中的JPython就沒有GIL。然而因?yàn)镃Python是大部分環(huán)境下默認(rèn)的Python執(zhí)行環(huán)境。所以在很多人的概念里CPython就是Python,也就想當(dāng)然的把GIL歸結(jié)為Python語言的缺陷。所以這里要先明確一點(diǎn):GIL并不是Python的特性,Python完全可以不依賴于GIL。
1.1 為什么要有GIL
相比于使用更細(xì)粒度的鎖,GIL具有以下優(yōu)點(diǎn):
1.2 GIL的運(yùn)作方式
1.3?GIL帶來的問題
因?yàn)橛蠫IL的存在,由CPython做解釋器(虛擬機(jī))的多線程Python程序只能利用多核處理器的一個(gè)核來運(yùn)行。
例如,我們將一個(gè)8線程的JAVA程序運(yùn)行在4核的處理器上,那么每個(gè)核會(huì)運(yùn)行1個(gè)線程,然后利用時(shí)間片輪轉(zhuǎn),輪流運(yùn)行每一個(gè)線程。但是,我們將一個(gè)8線程的Python程序(由CPython作解釋器)運(yùn)行在一個(gè)4核處理器上,那么總共只會(huì)有1個(gè)核在工作,8個(gè)線程都要在這一個(gè)核上面時(shí)間片輪轉(zhuǎn)。
所以說,用Python做多線程的最佳場景是處理I/O密集型應(yīng)用,例如網(wǎng)絡(luò)爬蟲,因?yàn)镮/O的速度比CPU的速度慢非常多,這樣不同線程的上下文切換(context switch)的時(shí)間就可以忽略不計(jì)了。如果做CPU密集型任務(wù),則使用多進(jìn)程可以更好地利用CPU的多核性能。另外,使用gevent協(xié)程可以避免多線程的上下文切換的問題
在python中使用都是操作系統(tǒng)級(jí)別的線程,linux中使用的pthread,window使用的是其原生線程
2.多線線程
多線程的好處自不必贅述,通過一個(gè)簡單的例子來看一下,參考關(guān)于播放音樂和視頻
先看看單線程的
#coding=utf-8 import threading from time import ctime,sleepdef music(func):for i in range(2):print("I was listening to %s. %s" %(func,ctime()))sleep(1)def move(func):for i in range(2):print("I was at the %s! %s" %(func,ctime()))sleep(5)if __name__ == '__main__':print("all start %s" %ctime())music(u'Music')move(u'Vedio')print("all over %s" %ctime())輸出如下:
但實(shí)際上詩就像MV一樣,這兩個(gè)是一般是同時(shí)開展的
#coding=utf-8 import threading from time import ctime,sleepdef music(func):for i in range(2):print("I was listening to %s. %s" %(func,ctime()))sleep(1)def move(func):for i in range(2):print("I was at the %s! %s" %(func,ctime()))sleep(5)threads = [] t1 = threading.Thread(target=music,args=(u'Music',)) threads.append(t1) t2 = threading.Thread(target=move,args=(u'Vedio',)) threads.append(t2)if __name__ == '__main__':print("all start %s" % ctime())for t in threads:t.setDaemon(True)t.start()t.join()print("all over %s" %ctime())?
1.join()方法,用于等待線程終止。join()的作用是,在子線程完成運(yùn)行之前,這個(gè)子線程的父線程將一直被阻塞等待子線程結(jié)束
2.程序中一般有主線程和子線程,兩種線程都執(zhí)行完畢,認(rèn)為是程序執(zhí)行結(jié)束。setDaemon(True)將線程聲明為守護(hù)線程,此類線程的特點(diǎn)是,當(dāng)程序中主線程及所有非守護(hù)線程執(zhí)行結(jié)束時(shí),未執(zhí)行完畢的守護(hù)線程也會(huì)隨之消亡(進(jìn)行死亡狀態(tài)),程序?qū)⒔Y(jié)束運(yùn)行。Python 解釋器的垃圾回收機(jī)制就是守護(hù)線程的典型代表,當(dāng)程序中所有主線程及非守護(hù)線程執(zhí)行完畢后,垃圾回收機(jī)制也就沒有再繼續(xù)執(zhí)行的必要了。需要注意的一點(diǎn)是,線程對(duì)象調(diào)用 daemon 屬性必須在調(diào)用 start() 方法之前,否則 Python 解釋器將報(bào) RuntimeError 錯(cuò)誤
參考?Python daemon守護(hù)線程詳解
輸入結(jié)果如下:
2.1 線程的調(diào)度和啟動(dòng)
python 中一個(gè)線程對(duì)應(yīng)c語言中一個(gè)線程。GIL使得同一個(gè)時(shí)刻只有一個(gè)線程運(yùn)行在cpu上執(zhí)行字節(jié)碼,無法將多個(gè)線程映射到多個(gè)cpu上執(zhí)行,GIL會(huì)根據(jù)執(zhí)行的字節(jié)碼行數(shù)以及時(shí)間釋放GIL,在遇到IO操作時(shí)會(huì)主動(dòng)釋放。python提供了_thread 和threading 兩個(gè)模塊來支持多線程,其中_thread提供低級(jí)別的,原始的線程支持,以及一個(gè)簡單的鎖,但是一般不建議使用_thread模塊;而threading模塊則提供了功能豐富的多線程支持。
通過一個(gè)對(duì)一個(gè)全局變量進(jìn)行增減操作,來解釋上述藍(lán)色字體描述
total = 0 def add(name):global totalfor i in range(10000):total += 1print('當(dāng)前是%s線程,i的值為%s' % (name,total))def desc(name):global totalfor i in range(10000):total -= 1print('當(dāng)前是%s線程,i的值為%s' % (name,total))import threading thread1 = threading.Thread(target=add,args=('thread1',)) thread2 = threading.Thread(target=desc,args=('thread2',))thread1.start() thread2.start()thread1.join() thread2.join()print(total)輸入結(jié)果如下所示:
從輸出結(jié)果來看,并不是等待其中一個(gè)thread跑完了再去跑另一個(gè)線程任務(wù)
3.線程構(gòu)造與使用
python 主要提供兩種方式來創(chuàng)建線程:
- 使用threading模塊的Thread類的構(gòu)造器創(chuàng)建線程
- 繼承 threading模塊的Thread類創(chuàng)建線程類
3.1調(diào)用Thread類構(gòu)造器創(chuàng)建線程
調(diào)用Thread類的構(gòu)造器創(chuàng)建線程,直接調(diào)用threading.Thread類的如下構(gòu)造器創(chuàng)建線程
__init__(self, group= None, target=None, name= None, args=(), kwargs= None, *, daemon= None)target :指定該線程要調(diào)度的目標(biāo)方法。只傳函數(shù)名,不傳函數(shù),即不加() args :指定一個(gè)元組,以位置參數(shù)的形式為target指定的函數(shù)傳入?yún)?shù)。元組的第一個(gè)參數(shù)傳給target的第一個(gè),以此類推。 kwargs:指定一個(gè)字典,以關(guān)鍵字參數(shù)的形式為target指定的函數(shù)傳入?yún)?shù) daemon: 指定所構(gòu)建的線程是否為后臺(tái)線程。調(diào)用Thread類的構(gòu)造器創(chuàng)建并啟動(dòng)多線程的步驟:
調(diào)用Thread類的構(gòu)造器創(chuàng)建線程對(duì)象。在創(chuàng)建線程對(duì)象時(shí)。target參數(shù)指定的函數(shù)將作為線程的執(zhí)行實(shí)體
調(diào)用線程對(duì)象的start()方法啟動(dòng)線程。
執(zhí)行結(jié)果如下:
可以看到在循環(huán)變量達(dá)到20時(shí),創(chuàng)建并且啟動(dòng)了兩個(gè)新線程。所以此時(shí)一共有三個(gè)線程,但是三個(gè)線程之間的執(zhí)行沒有沒有先后順序,他們的執(zhí)行方式為Thread-1執(zhí)行一段時(shí)間Thread-2或MainThread獲得CPU執(zhí)行一段時(shí)間。
- threading.current_thread():它是threading模塊的函數(shù),該函數(shù)總是返回當(dāng)前正在執(zhí)行的線程對(duì)象
- getName() 它是Thread類的實(shí)例方法,該方法返回調(diào)用它的線程名字
-setName() 方法可以為線程設(shè)置名字,getName和setName 兩個(gè)方法可通過name屬性來代替。
3.2繼承Thread類創(chuàng)建線程類
-
定義Thread類的子類,并重寫該類的run()方法。run()方法的方法體就代表了線程需要完成的任務(wù),因此把run()方法稱為線程執(zhí)行體。
-
創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建線程對(duì)象
-
調(diào)用線程對(duì)象的start()方法來啟動(dòng)線程
輸出結(jié)果如下:
?
參考 python中的GIL詳解
參考?python 多線程
參考?python并發(fā)編程
總結(jié)
以上是生活随笔為你收集整理的python多线程与GIL的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Django实现对数据库数据增删改查(二
- 下一篇: Python 内建函数 - sorted