• 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。

  • 相关阅读:
    (转)ab(apachebench)测试与loadrunner
    hibernate学习总结
    Oracle 11G在用EXP 导入、导出时,若有空表对导入导出中遇到的问题的解决
    Nginx可以做什么
    Oracle的表空间、用户和表的区别和联系
    oracle11g 导出表报EXP-00011:table不存在。
    tomcat、nginx、apache、tengine都是什么,及其作用
    注解和依赖注入框架
    js中innerHTML与innerText的用法与区别
    Linux中 /boot 目录介绍
  • 原文地址:https://www.cnblogs.com/shenh/p/14119971.html
Copyright © 2020-2023  润新知