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。

 

上一篇:Get小技巧等分列表


下一篇:利用协程计算质数