Python中gevent库(协程)的用法详解

后端 潘老师 3天前 11 ℃ (0) 扫码查看

gevent库作为Python并发编程一个强大的工具,能帮助我们实现并发同步或异步编程。今天,咱们就深入了解一下gevent库的具体用法。

一、gevent库简介

gevent库的核心模式是Greenlet,它是一种以C扩展模块形式接入Python的轻量级协程。简单来说,Greenlet就像是一个个小任务,它们都在主程序所在的操作系统进程中运行,不过这些小任务之间是协作调度的,不会互相抢占资源,这样就能让程序在处理多个任务时更加高效。

二、gevent库的安装

在使用gevent库之前,需要先进行安装。在命令行中执行以下命令即可完成安装:

pip install gevent

执行这个命令后,系统会自动下载并安装gevent库及其相关依赖。在安装过程中,你可能会看到类似下面这样的输出信息,显示正在收集和安装各个相关的包:

(emo is) C:\Users187\PycharmProjects\myproject>pip install gevent
collecting gevent
Using cached gevent-21.12.0-cp37-cp37m-win_amd64.whl(1.6 MB) 
Collecting zope.event
Using cached zope.event-4.5.0-py2.py3-none-any.whl (6.8 kB)
Requirement already satisfied: setuptools in d: \programdata\anaconda3\envs\emotional_analysis\lib\site-packages (from gevent)(49.6.0.post20200814) 
collecting greenlet<2.0,>=1.1.0;platform_python_implementation=="cpython"
Using cached greenlet-1.1.2-cp37-cp37m-win_amd64.whl (101 kB)
Requirement already satisfied: cffi>=1.12.2;platform_python_implementation=="cpython" and sys_platform=="win32" in d:\programdata\anaconda3\envs\e es (from gevent)(1.14.3)
Requirement already satisfied: zope.interface in d:\programdata\anaconda3\envs\emotional_analysis\lib\site-packages (from gevent)(5.4.0)

三、gevent库的使用示例

(一)基本使用示例

下面通过一个简单的示例,来看看gevent库的基本用法:

from gevent import monkey  # 为了能识别time模块的io

monkey.patch_all()  # 必须放到被打补丁者的前面,如 time,socket 模块之前
import gevent
import time


def gf(name):
    print(f'{name}:我想打王者!!')
    # gevent.sleep(2)
    time.sleep(2)
    print(f'{name}:我想吃大餐!!!')


def bf(name):
    print(f'{name}:一起打!!!')
    # gevent.sleep(2)
    time.sleep(2)
    print(f'{name}:一快去吃!!')


if __name__ == "__main__":
    start = time.time()
    # 创建协程对象
    g1 = gevent.spawn(gf, '张三')
    g2 = gevent.spawn(bf, '李四')

    # 开启任务
    g1.join()
    g2.join()
    end = time.time()
    print(end - start)

在这段代码中:

  • monkey.patch_all()的作用是对一些标准库进行补丁操作,让gevent能够识别time模块这类标准库中的IO操作,实现协程的切换。这个操作需要放在导入其他可能被打补丁的模块(如timesocket等)之前。
  • 定义了两个函数gfbf,函数内部先打印一句话,然后通过time.sleep(2)模拟一个耗时操作。这里如果使用gevent.sleep(2),效果会更明显,它会让当前协程暂停2秒,同时允许其他协程执行,但这里为了体现gevent对标准库time.sleep的处理能力,使用了标准库的time.sleep
  • if __name__ == "__main__":代码块中,创建了两个协程对象g1g2,分别绑定gf函数和bf函数,并传入不同的参数。然后通过join方法来等待这两个协程执行完毕,最后计算并打印整个过程所花费的时间。
    运行这段代码,你会看到类似下面的输出结果:
张三:我想打王者!!
李四:一起打!!!
张三:我想吃大餐!!!
李四:一快去吃!!
2.0309953689575195
Process finished with exit code 0 

从结果可以看出,两个协程是并发执行的,虽然每个函数内部都有2秒的等待时间,但总的执行时间大约是2秒,而不是4秒,这就体现了gevent库在并发处理上的优势。

(二)多协程切换示例

再来看一个更能体现协程切换的示例:

import gevent


def foo():
    print('Running in foo')
    gevent.sleep(2)
    print('Explicit context switch to foo again')


def bar():
    print('Explicit context to bar')
    gevent.sleep(2)
    print('Implicit context switch back to bar')


gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
])

在这个示例中:

  • 定义了foobar两个函数,函数内部都使用了gevent.sleep(2)来模拟耗时操作。gevent.sleep会使当前协程暂停执行,将执行权交给其他可运行的协程。
  • 使用gevent.joinall方法来启动并等待foobar两个协程执行完毕。gevent.joinall接受一个协程对象列表作为参数,它会阻塞当前线程,直到列表中的所有协程都执行完成。
    运行这段代码,输出结果如下:
Running in foo 
Explicit context to bar 
Explicit context switch to foo again 
Implicit context switch back to bar
Process finished with exit code 0

从输出结果可以清晰地看到,在foo函数执行过程中,遇到gevent.sleep(2)时,程序会切换到bar函数执行,等bar函数执行到gevent.sleep(2)时,又会切换回foo函数继续执行,这就是协程的上下文切换过程。

(三)基于gevent的Socket服务器示例(程序准确性待检查)

下面是一个使用gevent实现的简单Socket服务器示例:

from gevent import monkey, socket, pool

monkey.patch_all()


def server(port, pool):
    s = socket.socket()
    s.bind(('0.0.0.0', port))
    s.listen()
    while True:
        cli, addr = s.accept()
        print("Welcome %s to SocketServer" % str(addr[0]))
        # 通过pool.spawn()运行协程
        pool.spawn(handle_request, cli)


def handle_request(conn):
    try:
        data = conn.recv(1024)
        print("recv:", data)
        data = "From SockeServer:192.168.1.1---%s" % data.decode("utf-8")
        conn.sendall(bytes(data, encoding='utf-8'))
        if not data:
            conn.shutdown(socket.SHUT_WR)
    except Exception as e:
        print(e)
    finally:
        conn.close()


if __name__ == '__main__':
    # 限制并发协程数量为5
    pool = pool.Pool(5)
    server(80, pool)

在这个示例中:

  • 同样使用monkey.patch_all()对相关模块进行补丁操作,确保gevent能够正常工作。
  • server函数创建了一个Socket服务器,绑定到本地的80端口并开始监听。每当有新的客户端连接时,它会打印欢迎信息,并通过pool.spawn方法启动一个新的协程来处理客户端请求,这里的pool是一个协程池,限制了并发协程的数量为5。
  • handle_request函数负责处理客户端的具体请求,接收客户端发送的数据,对数据进行简单处理后再发送回客户端。如果接收不到数据,就关闭连接。
  • if __name__ == '__main__':代码块中,创建了一个最大容纳5个协程的协程池pool,并启动了服务器。

这个示例展示了gevent在网络编程中的应用,通过协程实现了高效的并发处理,不过需要注意的是,原文标注该程序准确性待检查,实际使用时可能需要进一步调试和优化。

通过以上内容,相信你对gevent库的基本用法有了更深入的了解。在实际开发中,根据不同的需求合理运用gevent库,能够有效提升程序的性能和并发处理能力。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/17901.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】