基礎概念
1. 定義:
纖程,微線程。是允許在不同入口點不同位置暫停或開始的計算機程序,簡單來說,協程就是可以暫停執行的函數。
2. 協程原理 :
記錄一個函數的上下文,協程調度切換時會將記錄的上下文保存,在切換回來時進行調取,恢復原有的執行內容,以便從上一次執行位置繼續執行。協程本質上就是一個線程,以前多線程任務的切換是由操作系統控制的,遇到I/O阻塞就自動切換,現在我們用協程的目的就是較少操作系統切換的開銷(開關線程,創建寄存器、堆棧等,在他們之間進行切換等),在我們自己的程序(應用層面)裏面來控制任務的切換。對於單線程下,我們不可避免程序中出現io操作,但如果我們能在自己的程序中(即用戶程序級別,而非操作系統級別)控制單線程下的多個任務能在一個任務遇到io阻塞時就切換到另外一個任務去計算,這樣就保證了該線程能夠最大限度地處於就緒態,即隨時都可以被cpu執行的狀態,相當於我們在用戶程序級別將自己的io操作最大限度地隱藏起來,從而可以迷惑操作系統,讓其看到:該線程好像是一直在計算,io比較少,從而更多的將cpu的執行權限分配給我們的線程。
3. 協程優缺點
優點
【1】協程完成多任務佔用計算資源很少
【2】由於協程的多任務切換在應用層完成,因此切換開銷少,最大限度地利用cpu
【3】協程爲單線程程序,無需進行共享資源同步互斥處理
缺點
協程的本質是一個單線程,無法利用計算機多核資源
標準庫協程的實現
python3.5以後,使用標準庫asyncio和async/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) # 協程方案