對(duì)于計(jì)算機(jī)而言,提高效率的方式其實(shí)是比較有限的,因?yàn)槌菑牡讓哟a(匯編代碼)進(jìn)行優(yōu)化,否則想要提升效率是比較困難的,只能通過(guò)不斷的優(yōu)化代碼和算法,提高效率。但實(shí)際上還有另外的方法也可以提高效率,來(lái)看看python有哪些提高效率的方法:
基礎(chǔ)概念介紹
進(jìn)程:進(jìn)程是一個(gè)程序的執(zhí)行實(shí)例,他是一個(gè)動(dòng)態(tài)的概念,是操作系統(tǒng)進(jìn)行資源分配的基本(最?。﹩挝?,一個(gè)進(jìn)程中包含了程序執(zhí)行過(guò)程中的所有資源。進(jìn)程之間數(shù)據(jù)交換需要使用到中間件。
線程:線程是CPU的最小調(diào)度單位,同一個(gè)進(jìn)程里面的線程共享所有資源(沒(méi)錯(cuò),一個(gè)進(jìn)程里可以有多個(gè)線程,每個(gè)線程都運(yùn)行在同一進(jìn)程的上下文中,共享同樣的代碼和全局?jǐn)?shù)據(jù),所以線程間的數(shù)據(jù)交換會(huì)來(lái)得容易些)。
協(xié)程:由于python存在GIL鎖,似的python的多線程沒(méi)有太多意義,這時(shí)候出現(xiàn)了自己操作線程的切換,以降低線程切換的開銷,這就是協(xié)程。
通俗理解線程進(jìn)程的關(guān)系:
進(jìn)程是連鎖店,線程是店里的灶臺(tái)
一個(gè)進(jìn)程可以有多個(gè)線程,——》一個(gè)連鎖店可以有多個(gè)灶臺(tái)
進(jìn)程間不能直接通信——》連鎖店之間通信是不方便的
線程間共享所有支援,可以直接通信——》灶臺(tái)之間共享店內(nèi)的所有食材
進(jìn)程要比線程消耗更多的計(jì)算機(jī)資源?!烽_個(gè)店比開個(gè)灶臺(tái)更貴
提升效率的方法一——多進(jìn)程
在上文的類比中,我們可以知道進(jìn)程是連鎖店,為了賺大錢(提高效率),我們可以多開幾家店,這就是多進(jìn)程技術(shù)。
在python中多進(jìn)程使用multiprocessing庫(kù)來(lái)實(shí)現(xiàn)。示例如下所示:
# import 多進(jìn)程庫(kù)
import multiprocessing
def worker1(name):
print("worker1 name is " + name)
def worker2(name):
print('worker2 name is' + name )
if __name__ == "__main__":
# target后面?zhèn)魅胍噙M(jìn)程的方法,args以元組的方式傳入?yún)?shù)
# 創(chuàng)建兩個(gè)進(jìn)程分別調(diào)用不同的方法
p1 = multiprocessing.Process(target=worker1, args=('subprocess1',))
p2 = multiprocessing.Process(target=worker2, args=('subprocess2'))
#啟動(dòng)進(jìn)程
p1.start()
p2.start()
#停止進(jìn)程
p1.join()
p2.join()
還記得上文提到的嘛?多進(jìn)程間進(jìn)程通信是比較困難的,但這并不代表沒(méi)有通信的手段,為了實(shí)現(xiàn)多進(jìn)程間的通信,我們可以使用隊(duì)列(Queue)來(lái)實(shí)現(xiàn),它是一種多進(jìn)程安全的隊(duì)列,有關(guān)他的更多用法可以查看python3教程中的相關(guān)文檔。
為什么要使用隊(duì)列來(lái)進(jìn)行通信?很簡(jiǎn)單,因?yàn)槿绻麤](méi)有通信,進(jìn)程之間就無(wú)法協(xié)作,會(huì)出現(xiàn)沖突,就像開連鎖店一樣,如果沒(méi)有協(xié)調(diào)好每個(gè)店的經(jīng)營(yíng)內(nèi)容,可能會(huì)出現(xiàn)互相搶客戶的現(xiàn)象。
提升效率的方法二——多線程
多線程技術(shù)在其他語(yǔ)言中是可以正常使用的,但在python中有例外,因?yàn)閜ython存在一個(gè)GIL鎖,它規(guī)定了線程在運(yùn)行時(shí),需要先拿到通行證,否則就不能運(yùn)行,也就意味著一個(gè)python的進(jìn)程里,無(wú)論你有多少個(gè)線程,永遠(yuǎn)只能單線程運(yùn)行。
還記得上文說(shuō)過(guò),線程是cpu最小調(diào)度單位嗎?也就意味著,python多線程是無(wú)法使用多核的,但是多進(jìn)程是可以利用多核的。
怎么理解GIL鎖呢,其實(shí)就是相當(dāng)于只有一個(gè)大師傅,雖然你有很多灶臺(tái),但是你只有一個(gè)人可以做菜。
那么python的多線程是不是沒(méi)用呢?不是,如果你的程序是CPU密集型的,那么python的多線程是完全沒(méi)有意義,甚至由于線程切換的花銷,會(huì)導(dǎo)致更慢點(diǎn)。
但如果你的是IO密集型,那么多線程的提升還是很明顯的。
io密集型,就是讀取數(shù)據(jù)比較耗費(fèi)時(shí)間,而cpu處理時(shí)間比較短,程序花費(fèi)的空閑的時(shí)間主要是cpu在等待io,這就是io密集型,比如等待網(wǎng)絡(luò)數(shù)據(jù),文件讀寫等
CPU密集型,就是處理數(shù)據(jù)比較耗費(fèi)時(shí)間,讀寫不耗費(fèi)時(shí)間,程序花費(fèi)的時(shí)間主要是cpu在處理數(shù)據(jù),而只有一小段時(shí)間是用在io上,這就是cpu密集型,比如算法運(yùn)算,復(fù)雜邏輯處理等等。
以大師傅為例,io密集型說(shuō)的就是食材的烹飪前處理、端菜上桌比較耗費(fèi)時(shí)間,cpu密集型說(shuō)的就是食材做成才比較耗費(fèi)時(shí)間。
雖然大師傅可以有很多個(gè)灶臺(tái),但大師傅只有一個(gè),cpu密集型的程序就相當(dāng)于大師傅要一直做菜,還要從一個(gè)灶臺(tái)跑到另一個(gè)灶臺(tái),大師傅會(huì)很累,而且同一時(shí)間內(nèi)大師傅只能在一個(gè)灶臺(tái)上做菜,所以實(shí)際上也沒(méi)有更快,因?yàn)榇髱煾颠€要跑來(lái)跑去,反而更慢了
io密集型的程序就相當(dāng)于大師傅做刺身(很簡(jiǎn)單的料理,但是提前處理材料很麻煩),每個(gè)灶臺(tái)都有自動(dòng)處理機(jī)器,它可以自動(dòng)把食材處理好,只要大師傅到位就可以做菜,做完也可以不用管,馬上切換到別的灶臺(tái)繼續(xù)做。所以多線程對(duì)于io密集型的程序提升確實(shí)是比較明顯的。
python的多線程使用的是threading庫(kù)(其實(shí)還有thread庫(kù),但這個(gè)庫(kù)比較簡(jiǎn)單,不推薦),示例如下所示:
# import 線程庫(kù)
import threading
# 這個(gè)函數(shù)名可隨便定義
def run(n):
print("current task:", n)
if __name__ == "__main__":
# 創(chuàng)建線程
t1 = threading.Thread(target=run, args=("thread 1",))
t2 = threading.Thread(target=run, args=("thread 2",))
t1.start()
t2.start()
協(xié)程介紹
協(xié)程是一種操作,原來(lái)多線程是由CPU控制的,而協(xié)程則是自己控制。當(dāng)代碼中出現(xiàn)有io處理的時(shí)候,先代碼自行調(diào)度,將這個(gè)操作掛起,然后去繼續(xù)執(zhí)行其他操作。
這樣的話,cpu就不會(huì)因?yàn)榇a中出現(xiàn)io處理進(jìn)行線程切換,從而減少線程切換的花銷,提升運(yùn)行速度。
大師傅在做菜的時(shí)候可能需要蒸十五分鐘,這十五分鐘大師傅完全可以去干別的,按照原來(lái)的多線程,大師傅得把這個(gè)灶頭的菜坐完再切換到別的灶頭,而協(xié)程的出現(xiàn)則改變了這個(gè)情況,大師傅發(fā)現(xiàn)蒸菜十五分鐘,他就去別的灶臺(tái)干別的活了,等到蒸好了再切換回來(lái)。
python的協(xié)程使用的asyncio庫(kù),示例代碼如下所示:
import asyncio
# 需要利用隊(duì)列來(lái)進(jìn)行協(xié)程之間的數(shù)據(jù)交換
queue = asyncio.Queue()
async def Producer():
n = 0
while True:
await asyncio.sleep(2)
print('add value to queue:',str(n))
await queue.put(n)
n = n + 1
async def Consumer():
while True:
try:
r = await asyncio.wait_for(queue.get(), timeout=1.0)
print('consumer value>>>>>>>>>>>>>>>>>>', r)
except asyncio.TimeoutError:
print('get value timeout')
continue
except:
break
print('quit')
loop = asyncio.get_event_loop()
tasks = [Producer(), Consumer()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
協(xié)程跟進(jìn)程、線程的區(qū)別
- 協(xié)程既不是進(jìn)程也不是線程,協(xié)程僅僅是一個(gè)特殊的函數(shù),協(xié)程它進(jìn)程和進(jìn)程不是一個(gè)維度的。
- 一個(gè)進(jìn)程可以包含多個(gè)線程,一個(gè)線程可以包含多個(gè)協(xié)程。
- 一個(gè)線程內(nèi)的多個(gè)協(xié)程雖然可以切換,但是多個(gè)協(xié)程是串行執(zhí)行的,只能在一個(gè)線程內(nèi)運(yùn)行,沒(méi)法利用CPU多核能力。
- 協(xié)程與進(jìn)程一樣,切換是存在上下文切換問(wèn)題的。
小結(jié)
以上就是有關(guān)于python并發(fā)編程的簡(jiǎn)單介紹了,想要更多了解python的并發(fā)編程,可以前往裴帥帥老師的新課程——Python 多線程多進(jìn)程多協(xié)程 并發(fā)編程實(shí)戰(zhàn)進(jìn)行學(xué)習(xí)!