本篇文章帶給大家的內容是關於python協程的詳細講解(附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
進程和執行緒都會的切換都要消耗時間,儲存執行緒進程目前狀態以便下次繼續執行。在不怎麼需要cpu的程序中,也就是相對於IO密集型的程序,協程相對於執行緒進程資源消耗更小,切換更快,更適用於IO密集型。協程也是單執行緒的,沒辦法利用cpu的多核心,想利用cpu多核心可以通過,行程 協程的方式,又或者行程 執行緒 協程。
1、協程的簡單實現
協程的原理是透過生成器實現,如下:程式執行到19行,執行consumer函數到13行,next產生器,執行producer函數到8行停下,返回consumer函數13行繼續往下執行,循環一次再次來到13行,生成器函數將從上次yield停下來的地方往下執行。循環如此就完成就完成了並發的效果。
但有人可能會說我用循環順序執行兩個函數結果也是一樣啊,如下第2例。當這裡想說的是,這樣並不能保留函數的執行的位置,只是簡單的一個函數執行結束換到另一個函數而已,遇到需要cpu等待的操作也沒辦法切換。在遇到需要cpu等待的操作主動讓出cpu,記住函數執行的位置,下次切換回來繼續執行才能算是並發的運行,提高程式的並發效果。
協程簡單實作生產者與消費者
import time def producer(): while True: time.sleep(1) print("+++++ 1个包子", time.strftime("%X")) yield def consumer(): while True: next(prd) print("----- 1个包子", time.strftime("%X")) if __name__ == "__main__": prd = producer() consumer() # 输出结果 +++++ 1个包子 16:22:30 ----- 1个包子 16:22:30 +++++ 1个包子 16:22:31 ----- 1个包子 16:22:31 +++++ 1个包子 16:22:32 ----- 1个包子 16:22:32 +++++ 1个包子 16:22:33 ----- 1个包子 16:22:33
順序執行效果
import time def producer(): time.sleep(1) print("+++++ 1个包子", time.strftime("%X")) def consumer(): print("----- 1个包子", time.strftime("%X")) if __name__ == "__main__": while True: producer() consumer() # 输出结果 +++++ 1个包子 16:22:30 ----- 1个包子 16:22:30 +++++ 1个包子 16:22:31 ----- 1个包子 16:22:31 +++++ 1个包子 16:22:32 ----- 1个包子 16:22:32 +++++ 1个包子 16:22:33 ----- 1个包子 16:22:33
2、greenlet
greenlet模組需要安裝,pip install greenlet。 greenlet原理是對生成器的封裝。 greenlet類別提供了一個方法,switch:在需要進行切換的時候切換到指定的協程。
from greenlet import greenlet import time def producer(): while True: time.sleep(1) print("+++++ 1个包子", time.strftime("%X")) gr2.switch() # 切换到gr2运行 def consumer(): while True: print("----- 1个包子", time.strftime("%X")) gr1.switch() # 切换到gr1运行 if __name__ == "__main__": gr1 = greenlet(producer) gr2 = greenlet(consumer) gr1.switch() # 切换到gr1运行 # 输出结果 +++++ 1个包子 09:39:45 ----- 1个包子 09:39:45 +++++ 1个包子 09:39:46 ----- 1个包子 09:39:46 +++++ 1个包子 09:39:47 ----- 1个包子 09:39:47
3、gevent
gevent模組也需要安裝,pip install gevent。 gevent是gevent的再次封裝,能自動辨識耗時操作切換到其它協程。注意gevent遇到耗時操作才會切換協程運行,沒有遇到耗時操作是不會主動切換的。
gevent.spawn(*args, **kwargs) 不定長參數中的第一個參數為協程執行的方法fn,其餘的依序為 fn 的參數。開啟了協程後要呼叫join方法。
gevent模組中辨識耗時的操作有兩種方式,① 使用gevent模組中重寫的類別。如,gevent.socket gevent.sleep ② 打補丁的方式,在所有的程式碼前。 from gevent import monkey 導入這個模組,monkey.patch_all()呼叫這個方法。
建議使用第二種方式,這樣就不用更改已經寫好的程式碼
正常情況下gevent並不會辨識耗時操作
import time import gevent def producer(): for i in range(3): time.sleep(1) print("+++++ 1个包子", name, time.strftime("%X")) def consumer(): for i in range(3): time.sleep(1) print("----- 1个包子", name, time.strftime("%X")) if __name__ == "__main__": g1 = gevent.spawn(producer, "zhangsan") g2 = gevent.spawn(consumer, "lisi") g1.join() g2.join() # 输出结果 +++++ 1个包子 zhangsan 10:42:38 +++++ 1个包子 zhangsan 10:42:39 +++++ 1个包子 zhangsan 10:42:40 ----- 1个包子 lisi 10:42:41 ----- 1个包子 lisi 10:42:42 ----- 1个包子 lisi 10:42:43
#gevent辨識耗時操作方式1,使用gevent中的模組
import time import gevent def producer(): for i in range(3): gevent.sleep(1) print("+++++ 1个包子", time.strftime("%X")) def consumer(): for i in range(3): gevent.sleep(1) print("----- 1个包子", time.strftime("%X")) if __name__ == "__main__": g1 = gevent.spawn(producer) g2 = gevent.spawn(consumer) g1.join() g2.join() # 输出结果 +++++ 1个包子 10:43:04 ----- 1个包子 10:43:04 +++++ 1个包子 10:43:05 ----- 1个包子 10:43:05 +++++ 1个包子 10:43:06 ----- 1个包子 10:43:06
gevent辨識耗時操作方式2,打補丁
import time import gevent from gevent import monkey monkey.patch_all() def producer(): for i in range(3): time.sleep(1) print("+++++ 1个包子", time.strftime("%X")) def consumer(): for i in range(3): time.sleep(1) print("----- 1个包子", time.strftime("%X")) if __name__ == "__main__": g1 = gevent.spawn(producer) g2 = gevent.spawn(consumer) g1.join() g2.join() # 输出结果 +++++ 1个包子 10:44:04 ----- 1个包子 10:44:04 +++++ 1个包子 10:44:05 ----- 1个包子 10:44:05 +++++ 1个包子 10:44:06 ----- 1个包子 10:44:06
當開啟的協程很多的時候,一個個的呼叫join方法就有點麻煩,所以gevent提供了一個方法joinall(),可以一次join所有的協程。 joinall() 方法傳參一個列表,列表包含了所有的協程。
joinall
import time import gevent from gevent import monkey monkey.patch_all() def producer(name): for i in range(3): time.sleep(1) print("+++++ 1个包子", name, time.strftime("%X")) def consumer(name): for i in range(3): time.sleep(1) print("----- 1个包子", name, time.strftime("%X")) if __name__ == "__main__": gevent.joinall([gevent.spawn(producer, "zhangsan"), gevent.spawn(consumer, "lisi")]) # 输出结果 +++++ 1个包子 zhangsan 10:51:34 ----- 1个包子 lisi 10:51:34 +++++ 1个包子 zhangsan 10:51:35 ----- 1个包子 lisi 10:51:35 +++++ 1个包子 zhangsan 10:51:36 ----- 1个包子 lisi 10:51:36
4、協程應用,並發伺服器
服務端收到客戶端訊息,並原樣發送回
server
import socket import gevent from gevent import monkey monkey.patch_all() def fn(conn): msg = conn.recv(1024).decode("utf-8") print("服务的收到>>>", msg) conn.send(msg.encode("utf-8")) sk = socket.socket() sk.bind(("127.0.0.1", 8899)) sk.listen() while True: conn, addr = sk.accept() print("已连接服务器-->", addr) gevent.spawn(fn, conn) sk.close() # 输出结果 已连接服务器--> ('127.0.0.1', 53878) 已连接服务器--> ('127.0.0.1', 53879) 已连接服务器--> ('127.0.0.1', 53880) 服务的收到>>> client1 服务的收到>>> client2 服务的收到>>> client3
clien
import socket sk = socket.socket() sk.connect(("127.0.0.1", 8899)) msg = input("客户端发送的内容>>> ") sk.send(msg.encode("utf-8")) msg = sk.recv(1024).decode("utf-8") print("客户端收到>>>", msg) sk.close() # 输出结果 客户端发送的内容>>> client1 客户端收到>>> client1
以上是python協程的詳細講解(附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!