Gevent/Eventlet 对数据库驱动的猴子补丁

9 投票
2 回答
5546 浏览
提问于 2025-04-17 13:28

在进行Gevent/Eventlet的猴子补丁后,我可以假设每当数据库驱动(比如 redis-pypymongo)通过标准库(比如 socket)进行输入输出时,它会是异步的吗?

所以,使用eventlet的猴子补丁就足够让比如 redis-py 在eventlet应用中变得非阻塞吗?

根据我的了解,如果我注意连接的使用(比如为每个绿色线程使用不同的连接),这应该就足够了。但我想确认一下。

如果你知道还需要什么,或者如何正确使用数据库驱动与Gevent/Eventlet,请也告诉我。

2 个回答

2

abarnert的回答是正确且非常全面的。我想补充一下,eventlet并没有“激进”的补丁,这可能是gevent的特点。另外,如果库使用了select,那也没问题,因为eventlet也可以对它进行猴子补丁。

实际上,在大多数情况下,eventlet.monkey_patch()就是你所需要的。当然,这个操作必须在创建任何套接字之前完成。

如果你还有任何问题,随时可以提问或者写邮件给eventlet的邮件列表,或者在G+社区发帖。所有相关链接可以在http://eventlet.net/找到。

18

你可以假设,如果以下所有条件都满足,它会神奇地被修复。

  • 你确定输入输出是基于标准的 Python socket 或其他 eventlet/gevent 会修改的东西。不能有文件,也不能有原生的 (C) socket 对象等。
  • 你在调用 patch_all(或 patch_select)时传入了 aggressive=True,或者你确定这个库没有使用 select 或类似的东西。
  • 驱动程序没有使用任何(隐式的)内部线程。(如果驱动程序确实使用了内部线程,patch_thread 可能有效,但也可能无效。)

如果你不确定,测试起来很简单——可能比阅读代码并试图理解要容易。你可以创建一个 greenlet,做一些这样的事情:

while True:
    print("running")
    gevent.sleep(0.1)

然后再创建一个 greenlet,执行一个慢查询。如果它被修改过,循环的 greenlet 会每秒打印 "running" 10 次;如果没有,循环的 greenlet 在查询阻塞时就不会运行。

那么,如果你的驱动程序阻塞了,你该怎么办呢?

最简单的解决方案是使用真正的并发线程池来处理数据库查询。这个想法是,你把每个查询(或批量查询)作为线程池的任务发出,然后在这个任务完成时让你的 gevent 阻塞。 (对于一些非常简单的情况,如果你不需要很多并发查询,你可以为每个查询直接创建一个 threading.Thread,但通常这样做不太可行。)

如果驱动程序进行了大量的 CPU 工作(例如,你使用的东西运行了一个进程内缓存,甚至是一个完整的进程内数据库管理系统,比如 sqlite),你希望这个线程池实际上是基于进程实现的,因为否则全局解释器锁(GIL)可能会阻止你的 greenlets 运行。否则(特别是如果你在乎 Windows),你可能想使用操作系统线程。(不过,这意味着你不能使用 patch_threads();如果需要这样做,就使用进程。)

如果你在使用 eventlet,并且想使用线程,有一个内置的简单解决方案叫做 tpool,可能就足够了。如果你在使用 gevent,或者需要使用进程,这个就不适用了。不幸的是,在真实的线程对象上阻塞一个 greenlet(而不阻塞整个事件循环)在 eventletgevent 之间有点不同,而且文档也不是很好,但 tpool 的源代码应该能给你一些启发。除此之外,其他的就是使用 concurrent.futures (如果你需要在 2.x 或 3.1 中使用,可以查看 futures)来在 ThreadPoolExecutorProcessPoolExecutor 上执行任务。(或者,如果你愿意,也可以直接使用 threadingmultiprocessing,而不使用 futures。)


你能解释一下为什么在 Windows 上应该使用操作系统线程吗?

简单来说:如果你坚持使用线程,你几乎可以编写跨平台的代码,但如果你使用进程,你实际上是在为两个不同的平台编写代码。

首先,阅读 multiprocessing 模块的 编程指南(包括“所有平台”部分和“Windows”部分)。幸运的是,数据库封装器应该不会遇到大多数问题。你只需要通过 ProcessPoolExecutor 来处理进程。无论你是在光标操作级别还是查询级别封装,所有的参数和返回值都将是可以被序列化的简单类型。不过,这仍然是你需要小心的地方,否则就不会有问题。

与此同时,Windows 的进程内同步对象开销非常低,但进程间的开销非常高。(它的线程创建速度非常快,而进程创建速度非常慢,但如果你使用池,这点就不重要了。)那么,你该如何处理呢?我曾经很开心地创建操作系统线程来等待跨进程的同步对象并信号给 greenlets,但你对“乐趣”的定义可能会有所不同。

最后,tpool 可以轻松地适配为 Unix 的 ppool,但在 Windows 上则需要更多的工作(而且你需要了解 Windows 才能完成这项工作)。

撰写回答