1.協程初識,greenlet模塊java
2.gevent模塊(須要pip安裝)python
一.協程初識,greenlet模塊:nginx
協程:是單線程下的併發,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。數據庫
greenlet模塊要本身用pip安裝編程
#協程: #本質上是一個線程 #可以在多個任務之間切換來節省一些IO時間 #協程中任務之間的切換也消耗時間,可是開銷遠遠小於進程線程之間的切換 #協程的意義: #在遇到IO操做的時候,切換到另一個任務 #規避以前任務的IO時間,來提升cpu的利用率 #在實際工做中會採用:進程+線程+協程,來提升代碼的併發效果 #進程是cpu核數+1,線程是cpu核數*5,每一個線程中協程最多能夠起500個 #好比: #發送了一個網頁請求後,在網絡延時,等待網頁響應的時間(等待IO),就能夠用協程去切換任務利用等待的時間,繼續發送多個網頁請求,從而提升效率 #進程5,線程20,協程500 = 總共能夠有50000個協程:一臺4c的機器最多能夠接收的併發數 #數據庫,負載均衡,讓不少個請求,平均分攤給各個服務器 #nginx組件 大型互聯網公司會用到,就是用來幫你分發任務的,併發最大承載量就是50000,用的就是協程機制 #通常狀況下就是根據這個規則,上下浮動 #協程的調度是由gevent完成的,而進程和線程的調度是cpu完成的 #greenlet之後都不怎麼樣,協程仍是主要用gevent #真正的協程模塊就是使用greenlet完成的切換 from greenlet import greenlet def eat(): print('eating') g2.switch() #切換到g2運行g2,切記錄當前g1運行的位置,切換後,若是g2那沒切換回來,後面這句 print('eat end') 就不會運行了 print('eat end') def play(): print('playing') g1.switch() print('play end') g1 = greenlet(eat) g2 = greenlet(play) g1.switch() # def play2(): # print('playing') # g1.switch() # print('play end') # # def ch(): # play2.__name__ = 'play3' # ch() # print(play2.__name__)
#協程
#因爲cpython解釋器中的GIL緣由,致使python中多線程被弱化了,並且切換多個線程之間也要時間開銷
#因此就出現了協程,協程的切換效率更快,把1個線程的做用發揮到了極致,提升1個cpu的利用率。減小線程的時間開銷
#java裏也有協程,可是沒有這麼被重視json
二.gevent模塊服務器
安裝:pip3 install gevent網絡
Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。多線程
#切記 from gevent import monkey;monkey.patch_all() 這句話必定要寫在想更改的模塊前面併發
#GKX #gevent模塊 join,joinall,value,spawn #協程適用於網絡延遲的時候,也就是適合作爬蟲的時候 ,或者socket鏈接的時候。代碼若是沒有高IO,不必用協程 #gevent只會識別它認識的IO操做 from gevent import monkey;monkey.patch_all() #monkey這句話必定要寫在想更改的模塊前面 import time #經過猴子補丁,讓sleep不阻塞 import gevent import threading # 用過gevent就會知道,會在最開頭的地方gevent.monkey.patch_all();把標準庫中的thread/socket等給替換掉. # 這樣咱們在後面使用socket的時候能夠跟日常同樣使用,無需修改任何代碼,可是它變成非阻塞的了. from greenlet import greenlet def eat(): print(threading.current_thread().getName()) #>>DummyThread-1 dummy(仿製品) print('eating') time.sleep(1) #使用猴子補丁後,至關於 gevent.sleep # gevent.sleep(1) print('eat end') def play(): print(threading.current_thread().getName()) print('playing') # time.sleep(1) gevent.sleep(1) print('play end') g1 = gevent.spawn(eat) #開啓一個協程, spawn(大量生產意思) g2 = gevent.spawn(play) g1.join() #讓線程等待協程的結果,若是沒有join,線程執行完畢後直接關閉,等不到協程的結果 g2.join() print('11111111111111') #遇到sleep後會非阻塞,先執行其餘任務,而後再同時來sleep1秒來打印任務 #有了gevent和猴子補丁,只要把函數註冊進 gevent模塊裏,就可使用協程了,其餘都不用管 #當遇到IO會自動幫你切換到其餘任務,最後再一塊兒共享,全部任務的IO操做 #遇到IO—執行其餘任務—其餘任務執行到也遇到了IO——繼續執行其餘任務—都遇到了IO—在IO之間來回切換,看誰解除了IO,立刻繼續運行 #從而把等待IO的時間,用來執行任務。而後共享IO時間,達到提升效率的目的 #咱們能夠不用理會gevent的執行過程,註冊完線程運行等待結果就行了
from gevent import monkey;monkey.patch_all() #monkey這句話必定要寫在想更改的模塊前面 import time #經過猴子補丁,讓sleep不阻塞 import gevent import threading #同步和異步 def task(): time.sleep(0.5) print('12345') def sync(): for i in range(10): task() def a_sync(): g_lst = [] for i in range(10): g = gevent.spawn(task) g_lst.append(g) gevent.joinall(g_lst) # == for g in g_lst:g.join() sync() a_sync()
# monkey patch指的是在運行時動態替換,通常是在startup的時候. # 用過gevent就會知道,會在最開頭的地方gevent.monkey.patch_all();把標準庫中的thread/socket等給替換掉. # 這樣咱們在後面使用socket的時候能夠跟日常同樣使用,無需修改任何代碼,可是它變成非阻塞的了. # 以前作的一個遊戲服務器,不少地方用的import json,後來發現ujson比自帶json快了N倍, # 因而問題來了,難道幾十個文件要一個個把import json改爲import ujson as json嗎? # 其實只須要在進程startup的地方monkey patch就好了.是影響整個進程空間的.同一進程空間中一個module只會被運行一次. # 下面是代碼: # import json # import ujson # # def monkey_patch_json(): # json.__name__ = 'ujson' # json.dumps = ujson.dumps # json.loads = ujson.loads # # monkey_patch_json() # # print # 'main.py', json.__name__ # import sub # # # import json # print 'sub.py',json.__name__ # 最後,注意不能單純的json = ujson來替換.
用gevent模塊實現的socket服務端的併發:
from gevent import monkey;monkey.patch_all()
from gevent import monkey;monkey.patch_all() import gevent def func(conn): conn.send(b'hello') msg = conn.recv(1024).decode('utf8') print(msg) conn.close() #關閉若是放在循環裏,就立刻關閉了,沒有鏈接的意義 while True: conn, addr = sk.accept() #在這裏阻塞,一直監聽,一旦有client鏈接,立刻把conn扔給一個併發。而後繼續循環,阻塞 g = gevent.spawn(func,conn) sk.close()
import time import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) print(sk.recv(1024)) msg = input('>>>> ').encode('utf8') sk.send(msg) sk.close()