咱們知道一個線程同一時間內只能被操做系統分配一個CPU資源, 咱們能夠基於多進程實現併發, 也能夠基於多線程實現併發, CPU正在運行一個任務, 有兩種狀況下會被切去執行其它任務, 一種是該任務發生了阻塞, 另外一是該任務運行時間過長或者被其餘優先級更高的任務奪走CPU, 對於單線程來講, 若是一個單線程下運行了多個任務, 那麼就不可避免的出現I/O操做, 一旦一個任務出現阻塞的狀況, 那麼整個線程將處於阻塞狀態(由於一旦阻塞, CPU資源將會被奪走), 可是若是咱們能在本身的應用程序中(即用戶級別, 非操做系統級別)控制單線程下多個任務能在一個任務遇到I/O以後立馬切換到另外一個任務, 那麼咱們就能夠保證該線程能最大限度的保持就緒狀態(也就是隨時能進入運行態), 至關於咱們在應用程序級別將I/O操做隱藏起來, 迷惑操做系統, 讓其看到的狀態就是一直在計算, I/O比較少, 因而操做系統就會將更多的CPU資源分配給該線程python
import time def Foo(): for i in range(100): print(f"--->Foo:{i}") yield # 保存狀態 def Bar(): f = Foo() for i in range(10000000): i += 1 next(f) start_time = time.time() Bar() stop_time = time.time() print(f"user time:{stop_time-start_time}")
ps : yield沒法檢測I/O, 沒法實現遇到I/O就進行切換程序員
ps : 如何實現自動檢測 I/O, 上面模擬使用的 yield 以及 greenlet 都沒法作到, 因而如下就開始介紹 gevent 模塊(select機制)編程
🍋"cmd" 或 "pycharm" 的 "Terminal" pip3 install gevent
Gevent是Python的第三方庫, 它爲各類併發和網絡相關的任務提供了整潔的API, 咱們能夠經過gevent輕鬆實現併發同步或異步編程網絡
在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程多線程
Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度併發
方法 | 做用 |
---|---|
gevent.spawn(func,args/kwargs) | 建立一個協程對象, 第一個參數是函數名, 後面的參數是函數的位置參數或者關鍵字參數 |
[協程對象].join( ) | 等待協程對象的結束 |
gevent.joinall([對象1,對象2]) | 等待多個協程對象的結束, 參數是一個列表, 放多個協程對象 |
[協程對象].value | 拿到協程對象的返回值 |
import gevent import time def eat(name): print(f"{name}正在吃東西") gevent.sleep(3) print(f"{name}吃完了") return "i am eat" def play(name): print(f"{name}正在玩手機") gevent.sleep(1) print(f"{name}玩夠了手機") return "i am play" start_time = time.time() g1 = gevent.spawn(eat,"派大星") g2 = gevent.spawn(play,"海綿寶寶") # g1.join() # 等待協程對象g1結束 # g2.join() # 等待協程對象g2結束 gevent.joinall([g1,g2]) # 等待協程對象g1和g2結束 print(g1.value) # 獲取協程對象g1的返回值 print(g2.value) # 獲取協程對象g2的返回值 print(f"用時:{time.time()-start_time}") '''輸出 派大星正在吃東西 海綿寶寶正在玩手機 海綿寶寶玩夠了手機 派大星吃完了 i am eat i am play 用時:3.0330262184143066 '''
像time.sleep(2)
或者其餘類型的I/O, gevent模塊沒法識別, 只能識別 gevent.sleep(2)
異步
解決方法 : 使用猴子補丁讓其能識別 from gevent import monkey;monkey.patch_all()
,放在文件開頭socket
from gevent import monkey;monkey.patch_all() import gevent import time from threading import current_thread def eat(name): print(current_thread().name) # 查看該線程的名字 print(f"{name}正在吃東西") time.sleep(3) print(f"{name}吃完了") return "i am eat" def drink(name): print(current_thread().name) # 查看該線程的名字 print(f"{name}正在喝湯") time.sleep(2) print(f"{name}把湯喝完了") return "i am drink" def play(name): print(current_thread().name) # 查看該線程的名字 print(f"{name}正在玩手機") time.sleep(1) print(f"{name}玩夠了手機") return "i am play" start_time = time.time() g1 = gevent.spawn(eat,"派大星") g2 = gevent.spawn(drink,"章魚哥") g3 = gevent.spawn(play,"海綿寶寶") # g1.join() # 等待協程對象g1結束 # g2.join() # 等待協程對象g2結束 # g3.join() # 等待協程對象g3結束 gevent.joinall([g1,g2,g3]) # 等待協程對象g1和g2結束 print(g1.value) # 獲取協程對象g1的返回值 print(g2.value) # 獲取協程對象g2的返回值 print(g3.value) # 獲取協程對象g3的返回值 print(f"用時:{time.time()-start_time}") '''輸出 Dummy-1 派大星正在吃東西 Dummy-2 章魚哥正在喝湯 Dummy-3 海綿寶寶正在玩手機 海綿寶寶玩夠了手機 章魚哥把湯喝完了 派大星吃完了 i am eat i am drink i am play 用時:3.0230190753936768 ''' 🍓# 能夠查看到三個線程的名字 : Dummy-一、Dummy-二、Dummy-三、(都是假線程)
from gevent import monkey;monkey.patch_all() # 添加猴子補丁 import gevent from socket import * # 建連接循環 def link(ip,port): try: server = socket(AF_INET, SOCK_STREAM) server.bind((ip,port)) server.listen(5) except Exception as E: print(E);return while 1: conn,addr = server.accept() gevent.spawn(communication,conn) # 創建連接成功以後開啓一個協程任務進行通訊循環 # 通訊循環 def communication(conn): while 1: try: data = conn.recv(1024) if len(data) == 0:break conn.send(data.upper()) except Exception as E: print(E);break conn.close() if __name__ == '__main__': g1 = gevent.spawn(link,"127.0.0.1",8090) # 先啓動一個創建連接循環的協程任務 g1.join()
from socket import * client = socket(AF_INET,SOCK_STREAM) client.connect(("127.0.0.1",8090)) while 1: user = input(">>").strip() if len(user) == 0:continue client.send(user.encode("utf-8")) data = client.recv(1024) print(data.decode("utf-8"))
from threading import Thread,current_thread from socket import * def connection(ip,port,i): client = socket(AF_INET,SOCK_STREAM) client.connect((ip,port)) while 1: client.send(f"客戶端編號:{i},名字:{current_thread().name}".encode("utf-8")) data = client.recv(1024) print(data.decode("utf-8")) if __name__ == '__main__': for i in range(10): # 多線程開啓 10 個客戶端進行與服務端的鏈接 t = Thread(target=connection,args=("127.0.0.1",8090,i)) t.start()