Python之協程技術

基礎概念

    1. 定義:

        纖程,微線程。是允許在不同入口點不同位置暫停或開始的計算機程序,簡單來說,協程就是可以暫停執行的函數。

    2. 協程原理 :

        記錄一個函數的上下文,協程調度切換時會將記錄的上下文保存,在切換回來時進行調取,恢復原有的執行內容,以便從上一次執行位置繼續執行。協程本質上就是一個線程,以前多線程任務的切換是由操作系統控制的,遇到I/O阻塞就自動切換,現在我們用協程的目的就是較少操作系統切換的開銷(開關線程,創建寄存器、堆棧等,在他們之間進行切換等),在我們自己的程序(應用層面)裏面來控制任務的切換。對於單線程下,我們不可避免程序中出現io操作,但如果我們能在自己的程序中(即用戶程序級別,而非操作系統級別)控制單線程下的多個任務能在一個任務遇到io阻塞時就切換到另外一個任務去計算,這樣就保證了該線程能夠最大限度地處於就緒態,即隨時都可以被cpu執行的狀態,相當於我們在用戶程序級別將自己的io操作最大限度地隱藏起來,從而可以迷惑操作系統,讓其看到:該線程好像是一直在計算,io比較少,從而更多的將cpu的執行權限分配給我們的線程。

    3. 協程優缺點

        優點

          【1】協程完成多任務佔用計算資源很少

          【2】由於協程的多任務切換在應用層完成,因此切換開銷少,最大限度地利用cpu

          【3】協程爲單線程程序,無需進行共享資源同步互斥處理

        缺點

          協程的本質是一個單線程,無法利用計算機多核資源

 

標準庫協程的實現

    python3.5以後,使用標準庫asyncioasync/await 語法來編寫併發代碼。asyncio庫通過對異步IO行爲的支持完成python的協程。雖然官方說asyncio是未來的開發方向,但是由於其生態不夠豐富,大量的客戶端不支持awaitable需要自己去封裝,所以在使用上存在缺陷。更多時候只能使用已有的異步庫(asyncio等),功能有限。

示例:

import asyncio


async def fun1():
    print("Start1")
    # 遇到阻塞跳出
    await asyncio.sleep(3)
    print("end1")


async def fun2():
    print("Start2")
    # 遇到阻塞跳出
    await asyncio.sleep(2)
    print("end2")


cor1 = fun1()
cor2 = fun2()

tasks = [asyncio.ensure_future(cor1),
         asyncio.ensure_future(cor2)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

# Start1
# Start2
# end2
# end1

 

第三方協程模塊

    【1】greenlet模塊

            安裝 : sudo pip3 install greenlet

            函數:

g = greenlet.greenlet(func)

功能:創建協程對象

參數:協程函數

返回值:greenlet 協程函數對象

g.switch()

功能:選擇要執行的協程函數

示例:
"""
    協程行爲展示
"""

from greenlet import greenlet


def fun1():
    print("執行fun1")
    # 跳轉執行fun2
    gr2.switch()
    print("結束fun1")
    # 跳回fun2繼續往下執行,而不是從fun2第一行開始
    gr2.switch()


def fun2():
    print("執行fun2")
    # 跳回fun1繼續往下執行,而不是從fun1第一行開始
    gr1.switch()
    print("結束fun2")


# 將函數變爲協成函數
gr1 = greenlet(fun1)
gr2 = greenlet(fun2)
gr1.switch()

【2】gevent模塊

           安裝:sudo pip3 install gevent

           函數:

gevent.spawn(func,argv)

功能: 生成協程對象

參數:func 協程函數

           argv 給協程函數傳參(不定參)

返回值: 協程對象

gevent.joinall(list,[timeout])

功能: 阻塞等待協程執行完畢

參數:list 協程對象列表

          timeout 超時時間

gevent.sleep(sec)

功能: gevent睡眠阻塞

參數:睡眠時間

* gevent協程只有在遇到gevent指定的阻塞行爲時纔會自動在協程之間進行跳轉,如gevent.joinall(),gevent.sleep()帶來的阻塞

         monkey腳本

            作用:

                  在gevent協程中,協程只有遇到gevent指定類型的阻塞才能跳轉到其他協程,因此,我們希望將普通的IO阻塞行爲轉換爲可以觸發gevent協程跳轉的阻塞,以提高執行效率。

            轉換方法:

                  gevent 提供了一個腳本程序monkey,可以修改底層解釋IO阻塞的行爲,將很多普通阻塞轉換爲gevent阻塞。  

            使用方法:

1】 導入monkey

from gevent import monkey

2】 運行相應的腳本,例如轉換socket中所有阻塞

monkey.patch_socket()

3】 如果將所有可轉換的IO阻塞全部轉換則運行all

monkey.patch_all()

4】 注意:腳本運行函數需要在對應模塊導入前執行

示例:

"""
    基於協程的tcp併發
"""
import gevent
from gevent import monkey

monkey.patch_socket()  # 執行腳本修改socket阻塞行爲
from socket import *


def handle(c):
    while True:
        data = c.recv(1024).decode()  # 屬於協程阻塞,不影響其他客戶端
        if not data:
            return
        print(data)
        c.send(b'OK')


# 創建套接字
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 8888))
s.listen(5)

# 循環接收來自客戶端請求
while True:
    c, addr = s.accept()
    print("Connect from", addr)
    # handle(c)  # 循環方案
    gevent.spawn(handle, c)  # 協程方案

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章