1、咱們知道不管是建立多進程仍是建立多線程池來解決問題,都要消耗必定的時間來建立進程、建立線程、以及管理他們之間的切換。python
基於單線程來實現併發,這樣就能夠節省建立線程進程所消耗的時間。編程
2、如何實如今兩個函數之間的切換?多線程
def func1(): print(1) yield print(3) yield def func2(): g = func1() next(g) print(2) next(g) print(4) func2() ''' 1 2 3 4 '''
def consumer(): while True: n = yield print('消費了一個包子%s'%n) def producer(): g = consumer() next(g) for i in range(5): print('生產了包子%s'%i) g.send(i) producer() ''' 生產了包子0 消費了一個包子0 生產了包子1 消費了一個包子1 生產了包子2 消費了一個包子2 生產了包子3 消費了一個包子3 生產了包子4 消費了一個包子4 '''
import time def consumer(): '''任務1:接收數據,處理數據''' while True: x=yield def producer(): '''任務2:生產數據''' g=consumer() next(g) for i in range(10000000): g.send(i) time.sleep(2) start=time.time() producer() #併發執行,可是任務producer遇到io就會阻塞住,並不會切到該線程內的其餘任務去執行 stop=time.time() print(stop-start)
對於單線程下,程序中不可避免的會出現io操做,但若是咱們能在本身的程序中(即用戶程序級別,而非操做系統級別)控制單線程下的多個任務能再一個任務遇到io阻塞時就切換到另一個任務去計算,這樣就保證了該線程可以最大限度地處於就緒態,即隨時均可以被cpu執行的狀態,至關於咱們在用戶程序級別將本身的io操做最大限度地隱藏起來,從而能夠迷惑操做系統,讓其看到:該線程好像是一直在計算,io比較少,從而更多的將cpu的執行權限分配給咱們的線程。併發
3、協程app
協程:是單線程下的併發,協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。異步
協程的本質:在但線程下,由用戶本身控制一個任務遇到io阻塞了就切換另一個任務去執行,以此來提高效率。socket
須要強調的是:async
1:python的線程屬於內核級別的,即由操做系統控制調度(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其它線程運行)。ide
2:單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,以此來提高效率(非io操做的切換與效率無關)異步編程
對比操做系統控制線程的切換,用戶在單線程內控制協程的切換的優缺點:
優勢:
1.協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級。
2.單線程內就能夠實現併發的效果,最大限度地利用cpu。
缺點:
1.協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程。
2.協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程。
協程的特色:
1.必須在只有一個單線程裏實現併發
2.修改共享數據不需加鎖
3.用戶程序裏本身保存多個控制流的上下文棧
4.附加:一個協程遇到IO操做自動切換到其它協程(如何實現檢測IO,yield、greenlet都沒法實現,就用到了gevent模塊(select機制))
4、greenlet模塊
from greenlet import greenlet def eat(name): print('%s eat 1' %name) g2.switch('haha') print('%s eat 2' %name) g2.switch() def play(name): print('%s play 1' %name) g1.switch() print('%s play 2' %name) g1=greenlet(eat) g2=greenlet(play) g1.switch('hjm')#能夠在第一次switch時傳入參數,之後都不須要 ''' hjm eat 1 haha play 1 hjm eat 2 haha play 2 '''
import time from greenlet import greenlet # 在單線程中切換狀態的模塊 def eat1(): print('吃雞腿1') g2.switch() time.sleep(5) print('吃雞翅2') g2.switch() def eat2(): print('吃餃子1') g1.switch() time.sleep(3) print('白切雞') g1 = greenlet(eat1) g2 = greenlet(eat2) g1.switch() ''' 吃雞腿1 吃餃子1 吃雞翅2 白切雞 '''
單純的切換(在沒有io的狀況下或者沒有重複開闢內存空間的操做),反而會下降程序的執行速度。
#順序執行 import time def f1(): res=1 for i in range(100000000): res+=i def f2(): res=1 for i in range(100000000): res*=i start=time.time() f1() f2() stop=time.time() print('run time is %s' %(stop-start)) # run time is 10.494175910949707 #切換 from greenlet import greenlet import time def f1(): res=1 for i in range(100000000): res+=i g2.switch() def f2(): res=1 for i in range(100000000): res*=i g1.switch() start=time.time() g1=greenlet(f1) g2=greenlet(f2) g1.switch() stop=time.time() print('run time is %s' %(stop-start)) # run time is 63.0725622177124
greenlet只是提供了一種比generator更加便捷的切換方式,當切到一個任務執行時若是遇到io,那就原地阻塞,仍然是沒有解決遇到IO自動切換來提高效率的問題。
5、gevent模塊
gevent是一個第三方庫,經過它能夠實現併發同步或異步編程。
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結束 #或者上述兩步合做一步:gevent.joinall([g1,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,'hjm') g2=gevent.spawn(play,name='hjm') g1.join() g2.join() #或者gevent.joinall([g1,g2]) print('主') ''' hjm eat 1 hjm play 1 hjm play 2 hjm eat 2 主 '''
上例gevent.sleep(2)模擬的是gevent能夠識別的io阻塞,而time.sleep(2)或其餘的阻塞,gevent是不能直接識別的須要用下面一行代碼,打補丁,就能夠識別了
from gevent import monkey;monkey.patch_all()必須放到被打補丁者的前面,如time,socket模塊以前
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') g1=gevent.spawn(eat) g2=gevent.spawn(play) gevent.joinall([g1,g2]) print('主')
from gevent import monkey;monkey.patch_all() import time # time socket urllib requests import gevent # greenlet gevent在切換程序的基礎上又實現了規避IO def task(args): time.sleep(1) print(args) def sync_func(): # 同步 for i in range(10): task(i) def async_func(): # 異步 g_l = [] for i in range(10): g_l.append(gevent.spawn(task,i)) # 給寫成任務傳參數 gevent.joinall(g_l) start = time.time() sync_func() print(time.time() - start) # 10.00815486907959 start = time.time() async_func() print(time.time() - start) # 1.0002970695495605
經過gevent實現單線程下的socket併發
from gevent import monkey monkey.patch_all() import gevent import socket def talk(conn): while True: ret = conn.recv(1024).decode('utf-8') print(ret) conn.send(ret.upper().encode('utf-8')) conn.close() sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while True: conn,addr = sk.accept() gevent.spawn(talk,conn) sk.close()
import time import socket import threading def my_client(): sk = socket.socket() sk.connect(('127.0.0.1',8080)) while True: sk.send(b'hi') ret = sk.recv(1024).decode('utf-8') print(ret) time.sleep(1) sk.close() for i in range(500): threading.Thread(target=my_client).start()