python協程的詳細講解(附範例)

不言
發布: 2018-10-08 17:41:10
轉載
5851 人瀏覽過

本篇文章帶給大家的內容是關於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中文網其他相關文章!

相關標籤:
來源:cnblogs.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!