什麼是協程
協程:是單線程下的併發,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。、html
須要強調的是:python
#1. python的線程屬於內核級別的,即由操做系統控制調度(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其餘線程運行) #2. 單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,以此來提高效率(!!!非io操做的切換與效率無關)
對比操做系統控制線程的切換,用戶在單線程內控制協程的切換程序員
優勢以下:編程
#1. 協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級 #2. 單線程內就能夠實現併發的效果,最大限度地利用cpu
缺點以下:併發
#1. 協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程 #2. 協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程
總結協程特色:app
- 必須在只有一個單線程裏實現併發
- 修改共享數據不需加鎖
- 用戶程序裏本身保存多個控制流的上下文棧
- 附加:一個協程遇到IO操做自動切換到其它協程(如何實現檢測IO,yield、greenlet都沒法實現,就用到了gevent模塊(select機制))
greenlet模塊
爲了實現同一線程內不一樣方法中任務能夠來回切換;別人寫的第三方模塊;安裝 :pip3 install greenlet異步

rom greenlet import greenlet def eat(): # 1 print('111') # 6 g2.switch() # 7 print('333') # 10 g2.switch() # 11 def sleep(): # 2 print('222') # 8 g1.switch() # 9 print('444') # 12 g1 = greenlet(eat) # 3 g2 = greenlet(sleep) # 4 g1.switch() # 5 greenlet狀態切換
進程:計算機中資源分配的最小單位 線程:計算機中cpu調度的最小單位 操做系統負責調度線程,對於操做系統來講,可見的最小單位就是線程; 線程的開銷比進程雖然小的多,可是開啓/關閉線程仍然須要開銷 協程:本質是一條線程,操做系統不可見,是由程序員操做的,而不是由操做系統調度的; 出現的意義:多個任務中的IO時間能夠共享,當執行一個任務遇到IO操做的時候,能夠將程序切換到另外一個任務中繼續執行,在有限的線程中,實現任務的併發,節省了調用操做系統建立/銷燬線程的時間,而且切成的切換效率比線程的切換效率要高,協程執行多個任務可以讓線程少陷入阻塞,讓線程看起來很忙,線程陷入的阻塞的次數越少,那麼可以搶佔cpu的資源就越多,你的程序效率就越高
gevent模塊
安裝:pip3 install geventsocket
Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。async

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的返回值 用法
gevent只有遇到IO操做纔會發生切換,且IO必須是gevent類中的;ide
# 遇到IO自由切換 import gevent def eat(): # 協程任務 協程函數 print('eat start') gevent.sleep(1) print('eat end') def sleep(): # 協程任務 協程函數 print('strat sleep') gevent.sleep(1) print('end sleep') g1 = gevent.spawn(eat) g2 = gevent.spawn(sleep) # gevent.sleep(1) # 只有遇到IO操做纔會切換 # g1.join() # 阻塞,直到g1任務執行完畢 # g2.join() # 阻塞,直到g2任務執行完畢 gevent.joinall([g1,g2]) # 等於g1.join()+g2.join()
若想使用其餘IO操做須要用from gevent import monkey;monkey.patch_all() 將IO打包 ;
rom gevent import monkey monkey.patch_all() import time import gevent def eat(): # 協程任務 協程函數 print('start eating') time.sleep(1) print('end eating') def sleep(): # 協程任務 協程函數 print('start sleeping') time.sleep(1) print('end sleeping') g1 = gevent.spawn(eat) # 建立協程 g2 = gevent.spawn(sleep) gevent.joinall([g1,g2]) # 阻塞 直到協程任務結束

# 請求網頁 url_dic = { '協程':'http://www.cnblogs.com/Eva-J/articles/8324673.html', '線程':'http://www.cnblogs.com/Eva-J/articles/8306047.html', '目錄':'https://www.cnblogs.com/Eva-J/p/7277026.html', '百度':'http://www.baidu.com', 'sogou':'http://www.sogou.com', '4399':'http://www.4399.com', '豆瓣':'http://www.douban.com', 'sina':'http://www.sina.com.cn', '淘寶':'http://www.taobao.com', 'JD':'http://www.JD.com' } import time from gevent import monkey;monkey.patch_all() from urllib.request import urlopen import gevent def get_html(name,url): ret = urlopen(url) content = ret.read() with open(name,'wb') as f: f.write(content) start = time.time() for name in url_dic: get_html(name+'_sync.html',url_dic[name]) ret = time.time() - start print('同步時間 :',ret) start = time.time() g_l = [] for name in url_dic: g = gevent.spawn(get_html,name+'_async.html',url_dic[name]) g_l.append(g) gevent.joinall(g_l) ret = time.time() - start print('異步時間 :',ret) 同步異步時間對比
協程實現socket server併發

from gevent import monkey;monkey.patch_all() import socket import gevent sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() def func(conn): # print(999) while True: msg = conn.recv(1024).decode('utf-8') print(msg) conn.send(b'hello') while True: print(888) conn,addr = sk.accept() print(000) g = gevent.spawn(func, conn) 由於每次鏈接一個客戶端都會進入循環,每次都會阻塞一次 因此才能繼續往下執行 server