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。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM