以前咱們學習了線程、進程的概念,瞭解了在操做系統中進程是資源分配的最小單位,線程是CPU調度的最小單位。按道理來講咱們已經算是把CPU的利用率提升不少了。可是咱們知道不管是建立多進程仍是建立多線程來解決問題,都要消耗必定的時間來建立進程、建立線程、以及管理他們之間的切換。python
隨着咱們對於效率的追求不斷提升,基於單線程來實現併發又成爲一個新的課題,即只用一個主線程(很明顯可利用的cpu只有一個)狀況下實現併發。這樣就能夠節省建立線進程所消耗的時間。編程
爲此咱們須要先回顧下併發的本質:切換+保存狀態。多線程
CPU正在運行一個任務,會在兩種狀況下切走去執行其餘的任務(切換由操做系統強制控制),一種狀況是該任務發生了阻塞,另一種狀況是該任務計算的時間過長。併發
ps:在介紹進程理論時,說起進程的三種執行狀態,而線程纔是執行單位,因此也能夠將上圖理解爲線程的三種狀態。異步
一:其中第二種狀況並不能提高效率,只是爲了讓cpu可以雨露均沾,實現看起來全部任務都被「同時」執行的效果,若是多個任務都是純計算的,這種切換反而會下降效率。socket
爲此咱們能夠基於yield來驗證。yield自己就是一種在單線程下能夠保存任務運行狀態的方法,咱們來簡單複習一下:異步編程
協程:是單線程下的併發,又稱微線程,纖程。英文名Coroutine。函數
一句話說明什麼是協程:協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。學習
須要強調的是:spa
對比操做系統控制線程的切換,用戶在單線程內控制協程的切換。
優勢:
缺點:
總結協程特色:
import time def func1(): while True: 1000000+1 yield def func2(): g = func1() for i in range(100000000): i+1 next(g) start = time.time() func2() stop = time.time() print(stop - start) # 28.522686004638672 ### 對比經過yeild切換運行的時間反而比串行更消耗時間,這樣實現的攜程是沒有意義的。 import time def func1(): for i in range(100000000): i+1 def func2(): for i in range(100000000): i+1 start = time.time() func1() func2() stop = time.time() print(stop - start) # 17.141255140304565
pip3 install gevent
Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。
g1=gevent.spawn(func,1,,2,3,x=4,y=5)建立一個協程對象g1,spawn括號內第一個參數是函數名,如eat,後面能夠有多個參數,能夠是位置實參或關鍵字實參,都是傳給函數eat的
g2=gevent.spawn(func2)
g1.join() #等待g1結束
g2.join() #等待g2結束
g1.value#拿到func1的返回值
import gevent def eat(name): print('%s eat 1' %name) gevent.sleep(2) print('%s eat 2' %name) def play(name): print('%s play 1' %name) gevent.sleep(1) print('%s play 2' %name) g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,name='egon') g1.join() g2.join() #或者gevent.joinall([g1,g2]) print('主')
上例gevent.sleep(2)模擬的是gevent能夠識別的io阻塞,
而time.sleep(2)或其餘的阻塞,gevent是不能直接識別的須要用下面一行代碼,打補丁,就能夠識別了
from gevent import monkey;monkey.patch_all()必須放到被打補丁者的前面,如time,socket模塊以前
或者咱們乾脆記憶成:要用gevent,須要將from gevent import monkey;monkey.patch_all()放到文件的開頭
from gevent import monkey monkey.patch_all() import gevent import time def eat(): print('eat food 1') time.sleep(2) print('eat food 2') def play(): print('play 1') time.sleep(1) print('play 2') start = time.time() g1 = gevent.spawn(eat) g2 = gevent.spawn(play) g1.join() g2.join() # gevent.joinall([g1,g2]) end = time.time() # 3.0165441036224365 # 若是打好了補丁 就能夠識別非gevent.sleep阻塞進行切換 print(end-start)