1. 程式人生 > 實用技巧 >python 協程程式設計之gevent

python 協程程式設計之gevent

  前言:協程又稱微執行緒,英文名coroutine。協程是使用者態的一種輕量級執行緒,是由使用者程式自己控制排程。基於這一原理,協程能在單執行緒下實現併發。我們知道程序是作業系統分配資源的基本單位,執行緒是CPU任務排程和執行的最小單位。執行緒之間的切換是由於執行緒A遇到了等待操作(比如I/O阻塞)或者計算時間過長,由作業系統控制切換到另一執行緒B。而協程在遇到阻塞的時候,通過使用者程式切換到另一協程,這個切換過程由程式控制,所以對作業系統來說是無感知的。相比較來說通過程式切換,比作業系統層面的切換,開銷要小的多的多。

協程的優點

  • 無需執行緒上下文切換的開銷
  • 無需原子操作(不會被執行緒排程機制打斷的操作)鎖定以及同步的開銷
  • 方便切換控制流,簡化程式設計模型
  • 適合高併發處理場景

協程的缺點

  • 無法利用多核資源:協程的本質是單執行緒,需要和程序配合才能執行在多CPU上
  • 進行阻塞(Blocking)操作(如I/O時)會阻塞掉整個程式

一、Gevent 基本使用

Gevent是一種協程的Python網路庫,基於Greenlet封裝了libevent事件迴圈的高層同步API。它讓我們在不改變程式設計習慣的同時,用同步的方式寫非同步I/O的程式碼。使用Gevent程式設計效能確實要比用傳統的執行緒高。

import gevent
from gevent import monkey
monkey.patch_all()
import time,datetime def test(tm): time.sleep(tm) print('時間:{}'.format(datetime.datetime.now())) if __name__ =='__main__': task = [] # 分配10個任務 for i in range(10): task.append(gevent.spawn(test,5)) # 阻塞主執行緒,直到所有協程執行完成 gevent.joinall(task)

二、monkey.patch_all

用過 gevent 的開發者都知道,在開頭匯入monkey.patch_all(),非常重要,會自動將 python 的一些標準模組替換成 gevent 框架,這個補丁其實存在著一些坑:

  • monkey.patch_all(),網上一般叫猴子補丁。如果使用了這個補丁,gevent 直接修改標準庫裡面大部分的阻塞式系統呼叫,包括 socket、ssl、threading 和 select 等模組,而變為協作式執行。有些地方使用標準庫會由於打了補丁而出現奇怪的問題(比如會影響 multiprocessing 的執行)
  • 和一些第三方庫不相容(比如不能相容 kazoo)。若要運用到專案中,必須確保其他用到的網路庫明確支援Gevent。

在實際情況中協程和程序的組合非常常見,兩個結合可以大幅提升效能,但直接使用猴子補丁會導致程序執行出現問題。其實可以按以下辦法解決,將 thread 置成 False,缺點是無法發揮 monkey.patch_all() 的全部效能:

import gevent
from gevent import monkey
monkey.patch_all(thread=False, socket=False, select=False)

三、Pool 限制併發

協程雖然是輕量級執行緒,但併發數達到一定量級後,會把系統的檔案控制代碼數佔光。所以需要通過 Pool 來限制併發數。

import gevent
from gevent.pool import Pool
from gevent import monkey
monkey.patch_all()
import time,datetime


def test(tm):
    time.sleep(tm)
    print('時間:{}'.format(datetime.datetime.now()))


if __name__ =='__main__':
    task = []
    # 限制最大併發
    pool = Pool(5)
    # 分配100個任務,最大併發數為5
    for i in range(100):
        task.append(pool.spawn(test,5))
    gevent.joinall(task)

執行結果:

時間:2020-11-20 17:08:15.625334
時間:2020-11-20 17:08:15.625334
時間:2020-11-20 17:08:15.625334
時間:2020-11-20 17:08:15.625334
時間:2020-11-20 17:08:15.625334
時間:2020-11-20 17:08:20.626347
時間:2020-11-20 17:08:20.626347
時間:2020-11-20 17:08:20.626347
時間:2020-11-20 17:08:20.626347
時間:2020-11-20 17:08:20.626347
時間:2020-11-20 17:08:25.627630
時間:2020-11-20 17:08:25.627630
時間:2020-11-20 17:08:25.627630
時間:2020-11-20 17:08:25.627630
時間:2020-11-20 17:08:25.627630
。。。

結語:gevent 雖“好”,“好”是指程式設計方便,只要在開頭使用 monkey.patch_all(),就能讓你的同步程式碼享受到非同步的效能。但坑也是存在的,所以複雜的業務場景不推薦使用 gevent,可以使用python 標準庫中的 asyncio。