協程:是單線程下的併發,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是協程:協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。
#1. python的線程屬於內核級別的,即由操做系統控制調度(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其餘線程運行) #2. 單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,以此來提高效率(!!!非io操做的切換與效率無關)
優勢
1. 協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級 #2. 單線程內就能夠實現併發的效果,最大限度地利用cpu
缺點
#1. 協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程 #2. 協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程
特色
必須在只有一個單線程裏實現併發 修改共享數據不需加鎖 用戶程序裏本身保存多個控制流的上下文棧 附加:一個協程遇到IO操做自動切換到其它協程(如何實現檢測IO,yield、greenlet都沒法實現,就用到了gevent模塊(select機制))
注意:進程線程的任務是由操做系統切換完的
協程任務之間是由程序切換 就是(本身寫的代碼)
協程本質就是切換多個任務之間節省一些IO操做(是一種僞線程)
由生成器引出協程
def cour(): print(11111) x=yield print(x) c=cour() print(c) # 是一個生成器函數 # <generator object cour at 0x0000027DEB9FFF68> # 進程已結束,退出代碼 0 print(***********************************************) import time def cour(): while True: x=yield time.sleep(1) print("處理數據",x) def pud(): c=cour() next(c) for i in range(5): print("生產了數據",i) c.send(i) pud() 生產了數據 0 處理數據 0 生產了數據 1 處理數據 1 生產了數據 2 處理數據 2 生產了數據 3 處理數據 3 生產了數據 4 處理數據 4 進程已結束,退出代碼 0
from greenlet import greenlet def eat(): print("開始吃飯了哈哈哈哈") g2.switch() # 切換到g2執行 print("飯吃完了哈哈哈哈") def play(): print("開始跑了哈哈哈啊哈") g1=greenlet(eat) # greenlet 表示開啓協程 g2=greenlet(play) # greenlet 表示開啓協程 g1.switch() # switch() 表示g1開始執行 # 開始吃飯了哈哈哈哈 # 開始跑了哈哈哈啊哈 # 進程已結束,退出代碼 0
注意協程本身程序調用 (就是本身寫的代碼調度執行) 不是由cpu或者操做系統調用
from greenlet import greenlet
def eat():
print("開始吃飯了哈哈哈哈")
g2.switch() # 切換到g2執行
print("飯吃完了哈哈哈哈")
g2.switch() # 切換到g2執行
def play():
print("8888888888888888")
g1.switch() # 切換到g1執行
print("9999999999999999")
g1=greenlet(eat) # greenlet 表示開啓協程
g2=greenlet(play) # greenlet 表示開啓協程
g1.switch() # switch() 表示g1開始執行
開始吃飯了哈哈哈哈
8888888888888888
飯吃完了哈哈哈哈
9999999999999999python
from greenlet import greenlet def eat(): print("開始吃飯了哈哈哈哈") g2.switch() # 切換到g2執行 print("飯吃完了哈哈哈哈") g2.switch() # 切換到g2執行 def play(): print("8888888888888888") g1.switch() # 切換到g1執行 print("9999999999999999") g1.switch() # 切換到g1執行 g1=greenlet(eat) # greenlet 表示開啓協程 g2=greenlet(play) # greenlet 表示開啓協程 g1.switch() # switch() 表示g1開始執行 g2.switch() # switch() 表示g1開始執行 開始吃飯了哈哈哈哈 8888888888888888 飯吃完了哈哈哈哈 9999999999999999 進程已結束,退出代碼
按照順序執行時間
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)) #10.985628366470337
按照協程切換執行時間
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)) # 52.763017892837524
注意 這裏面時間包含了切換時間 執行時間 切換時間大概4幾0秒左右 執行時間大概10幾秒左右
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.sleep(2)模擬的是gevent能夠識別的io阻塞,
# 而time.sleep(2)或其餘的阻塞,gevent是不能直接識別的須要用下面一行代碼,打補丁,就能夠識別了
# from gevent import monkey;monkey.patch_all()
import gevent,time def eat(): print("開始吃飯了哈哈哈哈") gevent.sleep(1) # time.sleep(1) print("飯吃完了哈哈哈哈") def play(): print("8888888888888888") gevent.sleep(1) # time.sleep(1) print("9999999999999999") g1=gevent.spawn(eat) # gevent.spawn表示開啓協程 g2=gevent.spawn(play) # gevent.spawn表示開啓協程 g1.join() 等待結束 g2.join() 等待結束 開始吃飯了哈哈哈哈 飯吃完了哈哈哈哈 8888888888888888 9999999999999999 進程已結束,退出代碼 0
# 如time,socket模塊以前 # 或者咱們乾脆記憶成:要用gevent,須要將from gevent import monkey;monkey.patch_all()放到文件的開頭
注意:進程線程的任務是由操做系統切換完的
協程任務之間是由程序切換 就是(本身寫的代碼)只有遇到協程模塊識別IO操做時候 程序纔會進行切換 實現併發
import gevent import time from gevent import monkey;monkey.patch_all() def eat(): print("開始吃飯了哈哈哈哈") time.sleep(1) print("飯吃完了哈哈哈哈") def play(): print("8888888888888888") time.sleep(1) print("9999999999999999") g1=gevent.spawn(eat) # gevent.spawn表示開啓協程 g2=gevent.spawn(play) # gevent.spawn表示開啓協程 g1.join() g2.join()
import gevent import time from gevent import monkey;monkey.patch_all() import threading def eat(): print(threading.current_thread().getName()) DummyThread-1 DummyThread-1 表示 僞類型線程 就是僞造出來的 print("開始吃飯了哈哈哈哈") time.sleep(1) print("飯吃完了哈哈哈哈") def play(): print("8888888888888888") time.sleep(1) print("9999999999999999") g1=gevent.spawn(eat) # gevent.spawn表示開啓協程 g2=gevent.spawn(play) # gevent.spawn表示開啓協程 g1.join() g2.join() DummyThread-1 # DummyThread-1 表示 僞類型線程 就是僞造出來的 開始吃飯了哈哈哈哈 8888888888888888 飯吃完了哈哈哈哈 9999999999999999
from gevent import spawn,joinall,monkey;monkey.patch_all() import time def task(pid): """ Some non-deterministic task """ time.sleep(0.5) print('Task %s done' % pid) def synchronous(): # 同步 for i in range(10): task(i) def asynchronous(): # 異步 g_l=[spawn(task,i) for i in range(10)] joinall(g_l) print('DONE') if __name__ == '__main__': print('Synchronous:') synchronous() print('Asynchronous:') asynchronous() # 上面程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn。 # 初始化的greenlet列表存放在數組threads中,此數組被傳給gevent.joinall 函數, # 後者阻塞當前流程,並執行全部給定的greenlet任務。執行流程只會在 全部greenlet執行完後纔會繼續向下走。
爬蟲案例
# 如time,socket模塊以前 # 或者咱們乾脆記憶成:要用gevent,須要將from gevent import monkey;monkey.patch_all()放到文件的開頭 # from gevent import spawn, joinall, monkey;monkey.patch_all() import gevent import time from urllib.request import urlopen def get_date(url): res=urlopen(url) cc=res.read().decode("utf-8") return len(cc) g1=gevent.spawn(get_date,"https://www.cnblogs.com/") g2=gevent.spawn(get_date,"https://www.sina.com.cn/") g3=gevent.spawn(get_date,"https://www.cnblogs.com/") g4=gevent.spawn(get_date,"https://www.cnblogs.com/") g5=gevent.spawn(get_date,"https://www.cnblogs.com/") aa=gevent.joinall([g1,g2,g3,g4,g5]) for i in aa: print(i.value) # 41781 # 41781 # 41781 # 535821 # 41781 # # 進程已結束,退出代碼 0
sever端 使用協程實現socket
# 如time,socket模塊以前 # 或者咱們乾脆記憶成:要用gevent,須要將from gevent import monkey;monkey.patch_all()放到文件的開頭 from gevent import spawn, joinall, monkey;monkey.patch_all() import gevent import time import socket def msg(conn): while True: aa=conn.recv(1024).decode("utf-8") print(aa) bb=input("接收:") conn.send(bb.encode("utf-8")) server=socket.socket() server.bind(("127.0.0.1",8700)) server.listen(4) while True: conn,addr=server.accept() g1=gevent.spawn(msg,conn)
client端 import socket cli=socket.socket() cli.connect(("127.0.0.1",8700)) while True: aa=input("發送:") cli.send(aa.encode("utf-8")) cl= cli.recv(1024).decode("utf-8") print(cl)