一、協程
1、又稱微線程,纖程。英文名Coroutine.一句話說明什么是協程:協程是一種用戶態的輕量級線程(相當於操作系統不知道它的存在,是用戶控制的)。
2、協程擁有自己的寄存器上下文和棧(代碼的必要的代碼段和)。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,因此:協程能保留上一次調用時的狀態(即所有局部狀態的一個特定組合),每次過程重入時,就相當於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。
3、協程一定是在單線程中運行的。
二、協程的優點與缺點
優點:
1、無需線程上下文切換的開銷。
2、無需原子操作(最小級別的操作)鎖定及同步的開銷。
3、方便切換控制流,簡化編程模型。
4、高並發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題,所以很適合用於高並發處理。
缺點:
1、無法利用多核資源:協程的本質是個單線程,它不能同時將單個CPU的多個核用上,協程需要和進程配合才能運行在多CPU上,當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
2、進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序。
三、使用yield實現協程操作例子
1、使用yield實現的一個最簡單的協程的效果
1 #!/usr/bin/python 2 # -*- coding : utf-8 -*- 3 # 作者: Presley 4 # 時間: 2018-12-4 5 # 郵箱:1209989516@qq.com 6 # 這是我用來練習python 協程的測試腳本 7 8 import time 9 import queue 10 11 def consumer(name): 12 print("starting eating baozi...") 13 while True: 14 new_baozi = yield 15 print("[%s] is eating baozi %s" %(name,new_baozi)) 16 17 def producer(): 18 r = con.__next__() 19 r = con2.__next__() 20 n = 0 21 while n < 5: 22 n += 1 23 con.send(n) 24 con2.send(n) 25 print("\033[32;1m[producer]\033[0m is making") 26 27 if __name__ == "__main__": 28 con = consumer("c1") 29 con2 = consumer("c2") 30 31 32 p = producer()
執行結果:
1 C:\Users\wohaoshuai\AppData\Local\Programs\Python\Python36\python.exe E:/PythonLearn/day16/pro_consume.py 2 starting eating baozi... 3 starting eating baozi... 4 [c1] is eating baozi 1 5 [c2] is eating baozi 1 6 [producer] is making 7 [c1] is eating baozi 2 8 [c2] is eating baozi 2 9 [producer] is making 10 [c1] is eating baozi 3 11 [c2] is eating baozi 3 12 [producer] is making 13 [c1] is eating baozi 4 14 [c2] is eating baozi 4 15 [producer] is making 16 [c1] is eating baozi 5 17 [c2] is eating baozi 5 18 [producer] is making 19 20 Process finished with exit code 0
2、greenlet
1 #!/usr/bin/python 2 # -*- coding : utf-8 -*- 3 # 作者: Presley 4 # 時間: 2018-12-4 5 # 郵箱:1209989516@qq.com 6 # 這是我用來練習python 協程的測試腳本 7 8 from greenlet import greenlet 9 10 def test1(): 11 print(12) 12 gr2.switch() 13 print(34) 14 gr2.switch() 15 16 def test2(): 17 print(56) 18 gr1.switch() 19 print(78) 20 21 gr1 = greenlet(test1) 22 gr2 = greenlet(test2) 23 gr1.switch()
執行結果:
1 C:\Users\wohaoshuai\AppData\Local\Programs\Python\Python36\python.exe E:/PythonLearn/day16/pro_consume.py 2 12 3 56 4 34 5 78 6 7 Process finished with exit code 0
3、gevent
a、gevent是一個第三方庫,可以輕松通過gevent實現並發同步或異步編程,在gevent中用到的主要模式是greenlet,它是以C擴展模塊形式接入Python的輕量級協程。greenlet全部運行在主程序操作系統進程的內部,但它們被協作式地調度。
1 #!/usr/bin/python 2 # -*- coding : utf-8 -*- 3 # 作者: Presley 4 # 時間: 2018-12-1 5 # 郵箱:1209989516@qq.com 6 # 這是我用來練習python 協程的測試腳本 7 8 import gevent 9 10 def foo(): 11 print("Running in foo") 12 gevent.sleep(1) 13 print("Explicit context switch to foo again") 14 15 def bar(): 16 print("Explicit context to bar") 17 gevent.sleep(1) 18 print("Implicit context switch back to bar") 19 20 def ex(): 21 print("Explicit context to ex") 22 gevent.sleep(1) 23 print("Implicit context switch back to ex") 24 25 gevent.joinall([ 26 gevent.spawn(foo), #類似產生一個協程的foo 27 gevent.spawn(bar), #產生一個協程的bar 28 gevent.spawn(ex) 29 ]) 30 31 #代碼的效果為:第一個協程切換到第二個,第二個切換到第三個,然后又遇到sleep(模擬io)又切換到下一個,然后實現並發的協程的效果
執行結果
1 C:\Users\wohaoshuai\AppData\Local\Programs\Python\Python36\python.exe E:/PythonLearn/day16/pro_consume.py 2 Running in foo 3 Explicit context to bar 4 Explicit context to ex 5 Explicit context switch to foo again 6 Implicit context switch back to bar 7 Implicit context switch back to ex 8 9 Process finished with exit code 0
b、通過協程爬取網頁實例
1 #!/usr/bin/python 2 # -*- coding : utf-8 -*- 3 # 作者: Presley 4 # 時間: 2018-12-5 5 # 郵箱:1209989516@qq.com 6 # 這是我用來練習python 協程的測試腳本 7 8 from gevent import monkey;monkey.patch_all() 9 import gevent 10 11 from urllib.request import urlopen 12 13 def f(url): 14 print("GET: %s" %url) 15 resp = urlopen(url) 16 data = resp.read() 17 print("%d bytes received from %s." %(len(data),url)) 18 19 gevent.joinall([ 20 gevent.spawn(f,"https://www.python.org/"), 21 gevent.spawn(f,"https://www.yahoo.com/"), 22 gevent.spawn(f,"https://github.com"), 23 ])
執行結果:
1 C:\Users\wohaoshuai\AppData\Local\Programs\Python\Python36\python.exe E:/PythonLearn/day16/pro_consume.py 2 GET: https://www.python.org/ 3 GET: https://www.yahoo.com/ 4 GET: https://github.com 5 80704 bytes received from https://github.com. 6 50008 bytes received from https://www.python.org/. 7 528149 bytes received from https://www.yahoo.com/. 8 9 Process finished with exit code 0
c、通過gevent實現單線程下的多socket並發
server端
1 #!/usr/bin/python 2 # -*- coding : utf-8 -*- 3 # 作者: Presley 4 # 時間: 2018-12-5 5 # 郵箱:1209989516@qq.com 6 # 這是我用來練習python 協程的測試腳本 7 8 9 import gevent 10 from gevent import socket,monkey 11 monkey.patch_all() #python中的一種黑魔法,只要寫入一句話就自動的把python中的許多標准庫變為非阻塞的模式 12 13 def server(port): 14 s = socket.socket() 15 s.bind(("0.0.0.0",port)) 16 s.listen(5000) 17 while True: 18 cli,addr = s.accept() 19 gevent.spawn(handle_request,cli) #執行handle_request函數,參數是cli,即客戶端實例 20 def handle_request(s): 21 try: 22 while True: 23 data = s.recv(1024) #接收數據,這里設置成不阻塞 24 print("recv:",data) 25 s.send(data) 26 if not data: 27 s.shutdown(socket.SHUT_RD) #如果接收為空值,結束 28 except Exception as ex: 29 print(ex) 30 finally: 31 s.close() 32 33 if __name__ == "__main__": 34 server(8001)
client端
1 #!/usr/bin/python 2 # -*- coding : utf-8 -*- 3 # 作者: Presley 4 # 時間: 2018-12-5 5 # 郵箱:1209989516@qq.com 6 # 這是我用來練習python 協程的測試腳本 7 8 import socket 9 10 HOST = "localhost" 11 PORT = 8001 12 s = socket.socket() 13 s.connect((HOST,PORT)) 14 15 while True: 16 msg = input(">>:") 17 if not msg:continue 18 msg = msg.encode("utf-8") 19 s.sendall(msg) 20 data = s.recv(1024) 21 print("Received",data.decode("utf-8")) 22 s.close()