併發程式設計之協程
一、協程的介紹
協程:是單執行緒下的併發,一句話說明什麼是執行緒:協程是一種使用者態的輕量級執行緒,即協程是由使用者程式自己控制排程的。
對比作業系統控制執行緒的切換,使用者在單執行緒內控制協程的切換
優點:
#1. 協程的切換開銷更小,屬於程式級別的切換,作業系統完全感知不到,因而更加輕量級 #2. 單執行緒內就可以實現併發的效果,最大限度地利用cpu
缺點:
#1. 協程的本質是單執行緒下,無法利用多核,可以是一個程式開啟多個程序,每個程序內開啟多個執行緒,每個執行緒內開啟協程 #2. 協程指的是單個執行緒,因而一旦協程出現阻塞,將會阻塞整個執行緒
總結協程特點:
1、必須在只有一個單執行緒裡實現併發
2、修改共享資料不需加鎖
3、使用者程式裡自己儲存多個控制流的上下文棧
4、附加:一個協程遇到IO操作自動切換到其它協程(如何實現檢測IO,yield、greenlet都無法實現,就用到了gevent模組(select機制))
協程的實現:
import time def task(): while True: print("task1") time.sleep(4) yield 1 def task2(): g = task() while True: try: print("task2") next(g) except Exception: print("任務完成") break task2()
應用場景:
TCP 多客戶端實現方式 1.來一個客戶端就來一個程序資源消耗較大 2.來一個客戶端就來一個執行緒也不能無限開 3.用程序池 或 執行緒池還是一個執行緒或程序只能維護一個連線 4.協程 一個執行緒就可以處理多個客戶端遇到io就切到另一個
二、greenlet模組
import greenlet import time def task1(): print("task1 1") time.sleep(2) g2.switch() print("task1 2") g2.switch() def task2(): print("task2 1") g1.switch() print("task2 2") g1 = greenlet.greenlet(task1) g2 = greenlet.greenlet(task2) g1.switch() # 1.例項化greenlet得到一個物件 傳入要執行的任務 #至少需要兩個任務 # 2.先讓某個任務執行起來 使用物件呼叫switch # 3.在任務的執行過程中 手動呼叫switch來切換
greenlet只是提供了一種比generator更加便捷的切換方式,當切到一個任務執行時如果遇到io,那就原地阻塞,仍然是沒有解決遇到IO自動切換來提升效率的問題。
單執行緒裡的多個任務的程式碼通常會既有計算操作又有阻塞操作,我們完全可以在執行任務1時遇到阻塞,就利用阻塞的時間去執行任務2。。。。如此,才能提高效率,這就用到了Gevent模組。
三、gevent模組
Gevent 是一個第三方庫,可以輕鬆通過gevent實現併發同步或非同步程式設計,在gevent中用到的主要模式是Greenlet , 它是以C擴充套件模組形式接入Python的輕量級協程。 Greenlet全部執行在主程式作業系統程序的內部,但它們被協作式地排程。
from gevent import monkey monkey.patch_all() import gevent import time def eat(): print('eat food 1') time.sleep(2) # gevent.sleep(1) print('eat food 2') def play(): print('play 1') time.sleep(1) # gevent.sleep(1) print('play 2') g1=gevent.spawn(eat) g2=gevent.spawn(play) # g1.join() # g2.join() gevent.joinall([g1,g2]) print('主') # 1.spawn函式傳入你的任務 # 2.呼叫join 去開啟任務 # 3.檢測io操作需要打monkey補丁就是一個函式 在程式最開始的地方呼叫它
練習:
使用協程完成TCP套接字程式設計 支援多客戶端同時訪問
import gevent from gevent import monkey monkey.patch_all() import socket server = socket.socket() # 重用埠 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(("127.0.0.1",9999)) server.listen(5) def data_handler(conn): print("一個新連線..") while True: data = conn.recv(1024) conn.send(data.upper()) while True: conn,addr = server.accept() # 切到處理資料的任務去執行 gevent.spawn(data_handler,conn) 伺服器
import socket c = socket.socket() c.connect(("127.0.0.1",9999)) while True: msg = input(">>>:") if not msg:continue c.send(msg.encode("utf-8")) data = c.recv(1024) print(data.decode("utf-8")) 客戶端