對于計算機而言,提高效率的方式其實是比較有限的,因為除非從底層代碼(匯編代碼)進行優(yōu)化,否則想要提升效率是比較困難的,只能通過不斷的優(yōu)化代碼和算法,提高效率。但實際上還有另外的方法也可以提高效率,來看看python有哪些提高效率的方法:
基礎概念介紹
進程:進程是一個程序的執(zhí)行實例,他是一個動態(tài)的概念,是操作系統(tǒng)進行資源分配的基本(最?。﹩挝唬粋€進程中包含了程序執(zhí)行過程中的所有資源。進程之間數(shù)據(jù)交換需要使用到中間件。
線程:線程是CPU的最小調度單位,同一個進程里面的線程共享所有資源(沒錯,一個進程里可以有多個線程,每個線程都運行在同一進程的上下文中,共享同樣的代碼和全局數(shù)據(jù),所以線程間的數(shù)據(jù)交換會來得容易些)。
協(xié)程:由于python存在GIL鎖,似的python的多線程沒有太多意義,這時候出現(xiàn)了自己操作線程的切換,以降低線程切換的開銷,這就是協(xié)程。
通俗理解線程進程的關系:
進程是連鎖店,線程是店里的灶臺
一個進程可以有多個線程,——》一個連鎖店可以有多個灶臺
進程間不能直接通信——》連鎖店之間通信是不方便的
線程間共享所有支援,可以直接通信——》灶臺之間共享店內的所有食材
進程要比線程消耗更多的計算機資源?!烽_個店比開個灶臺更貴
提升效率的方法一——多進程
在上文的類比中,我們可以知道進程是連鎖店,為了賺大錢(提高效率),我們可以多開幾家店,這就是多進程技術。
在python中多進程使用multiprocessing庫來實現(xiàn)。示例如下所示:
# import 多進程庫
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程的方法,args以元組的方式傳入參數(shù)
# 創(chuàng)建兩個進程分別調用不同的方法
p1 = multiprocessing.Process(target=worker1, args=('subprocess1',))
p2 = multiprocessing.Process(target=worker2, args=('subprocess2'))
#啟動進程
p1.start()
p2.start()
#停止進程
p1.join()
p2.join()
還記得上文提到的嘛?多進程間進程通信是比較困難的,但這并不代表沒有通信的手段,為了實現(xiàn)多進程間的通信,我們可以使用隊列(Queue)來實現(xiàn),它是一種多進程安全的隊列,有關他的更多用法可以查看python3教程中的相關文檔。
為什么要使用隊列來進行通信?很簡單,因為如果沒有通信,進程之間就無法協(xié)作,會出現(xiàn)沖突,就像開連鎖店一樣,如果沒有協(xié)調好每個店的經營內容,可能會出現(xiàn)互相搶客戶的現(xiàn)象。
提升效率的方法二——多線程
多線程技術在其他語言中是可以正常使用的,但在python中有例外,因為python存在一個GIL鎖,它規(guī)定了線程在運行時,需要先拿到通行證,否則就不能運行,也就意味著一個python的進程里,無論你有多少個線程,永遠只能單線程運行。
還記得上文說過,線程是cpu最小調度單位嗎?也就意味著,python多線程是無法使用多核的,但是多進程是可以利用多核的。
怎么理解GIL鎖呢,其實就是相當于只有一個大師傅,雖然你有很多灶臺,但是你只有一個人可以做菜。
那么python的多線程是不是沒用呢?不是,如果你的程序是CPU密集型的,那么python的多線程是完全沒有意義,甚至由于線程切換的花銷,會導致更慢點。
但如果你的是IO密集型,那么多線程的提升還是很明顯的。
io密集型,就是讀取數(shù)據(jù)比較耗費時間,而cpu處理時間比較短,程序花費的空閑的時間主要是cpu在等待io,這就是io密集型,比如等待網絡數(shù)據(jù),文件讀寫等
CPU密集型,就是處理數(shù)據(jù)比較耗費時間,讀寫不耗費時間,程序花費的時間主要是cpu在處理數(shù)據(jù),而只有一小段時間是用在io上,這就是cpu密集型,比如算法運算,復雜邏輯處理等等。
以大師傅為例,io密集型說的就是食材的烹飪前處理、端菜上桌比較耗費時間,cpu密集型說的就是食材做成才比較耗費時間。
雖然大師傅可以有很多個灶臺,但大師傅只有一個,cpu密集型的程序就相當于大師傅要一直做菜,還要從一個灶臺跑到另一個灶臺,大師傅會很累,而且同一時間內大師傅只能在一個灶臺上做菜,所以實際上也沒有更快,因為大師傅還要跑來跑去,反而更慢了
io密集型的程序就相當于大師傅做刺身(很簡單的料理,但是提前處理材料很麻煩),每個灶臺都有自動處理機器,它可以自動把食材處理好,只要大師傅到位就可以做菜,做完也可以不用管,馬上切換到別的灶臺繼續(xù)做。所以多線程對于io密集型的程序提升確實是比較明顯的。
python的多線程使用的是threading庫(其實還有thread庫,但這個庫比較簡單,不推薦),示例如下所示:
# import 線程庫
import threading
# 這個函數(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é)程是一種操作,原來多線程是由CPU控制的,而協(xié)程則是自己控制。當代碼中出現(xiàn)有io處理的時候,先代碼自行調度,將這個操作掛起,然后去繼續(xù)執(zhí)行其他操作。
這樣的話,cpu就不會因為代碼中出現(xiàn)io處理進行線程切換,從而減少線程切換的花銷,提升運行速度。
大師傅在做菜的時候可能需要蒸十五分鐘,這十五分鐘大師傅完全可以去干別的,按照原來的多線程,大師傅得把這個灶頭的菜坐完再切換到別的灶頭,而協(xié)程的出現(xiàn)則改變了這個情況,大師傅發(fā)現(xiàn)蒸菜十五分鐘,他就去別的灶臺干別的活了,等到蒸好了再切換回來。
python的協(xié)程使用的asyncio庫,示例代碼如下所示:
import asyncio
# 需要利用隊列來進行協(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é)程跟進程、線程的區(qū)別
- 協(xié)程既不是進程也不是線程,協(xié)程僅僅是一個特殊的函數(shù),協(xié)程它進程和進程不是一個維度的。
- 一個進程可以包含多個線程,一個線程可以包含多個協(xié)程。
- 一個線程內的多個協(xié)程雖然可以切換,但是多個協(xié)程是串行執(zhí)行的,只能在一個線程內運行,沒法利用CPU多核能力。
- 協(xié)程與進程一樣,切換是存在上下文切換問題的。
小結
以上就是有關于python并發(fā)編程的簡單介紹了,想要更多了解python的并發(fā)編程,可以前往裴帥帥老師的新課程——Python 多線程多進程多協(xié)程 并發(fā)編程實戰(zhàn)進行學習!