Gevent/Eventlet猴子补丁用于数据库驱动程序

9

进行 Gevent/Eventlet 代码猴子补丁后 - 我能否假设无论何时 DB 驱动程序(例如redis-pypymongo)通过标准库(例如 socket)使用 IO,它都是异步的?

因此,在事件应用程序中使用事件程序员的猴子补丁就足以使例如:redis-py非阻塞吗?

从我所知道的来看,如果我注意连接使用(例如为每个 greenlet 使用不同的连接),那么应该足够了。但我想确定。

如果您知道还需要什么,或者如何在 Gevent/Eventlet 中正确使用 DB 驱动程序,请一并说明。

2个回答

18

如果以下所有条件都成立,您可以假设它将被自动修补。

  • 您确定I/O是基于标准Python socket或其他eventlet/gevent猴子补丁构建的。没有文件,没有本地(C)套接字对象等。
  • 您将aggressive=True传递给patch_all(或patch_select),或者您确定该库不使用select或任何类似的内容。
  • 驱动程序不使用任何(隐式)内部线程。(如果驱动程序在内部使用线程,则patch_thread可能有效,但也可能无效。)

如果您不确定,测试起来非常容易-可能比阅读代码并尝试解决问题更容易。有一个绿色线程只需执行以下操作即可:

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

然后再运行一个针对数据库的慢查询。如果进行了猴子补丁,循环的绿色线程将会每秒打印10次“running”;如果没有进行猴子补丁,则在程序被阻塞的时候,循环的绿色线程将无法运行。
那么,如果您的驱动程序被阻塞了该怎么办呢?
最简单的解决方案是使用真正并发的线程池进行DB查询。这个想法是将每个查询(或批处理)作为线程池作业启动,并在该作业完成时使用greenlet-block gevent。(对于非常简单的情况,您可以为每个查询仅生成一个threading.Thread,但通常您无法这样做。)
如果驱动程序执行了重要的CPU工作(例如,您正在使用运行在进程中的缓存或甚至整个进程内的DBMS,例如sqlite),则您希望这个线程池实际上是在进程之上实现的,因为否则GIL可能会阻止您的greenlets运行。否则(特别是如果您关心Windows),您可能希望使用OS线程。(但是,这意味着您无法patch_threads();如果需要这样做,请使用进程。)
如果您使用的是 eventlet,并且想要使用线程,则有一个内置的简单解决方案叫做 tpool,可能已经足够了。如果您使用的是 gevent,或者需要使用进程,则这种方法不适用。不幸的是,在真正的线程对象上阻塞 greenlet(而不阻塞整个事件循环)在 eventletgevent 中略有不同,并且没有很好的文档记录,但是 tpool 源代码应该能给您提供思路。除此之外,其余部分只需使用 concurrent.futures(如果您需要在 2.x 或 3.1 中使用,请参见 pypi 上的 futures)在 ThreadPoolExecutorProcessPoolExecutor 上执行任务即可。(或者,如果您喜欢,可以直接使用 threadingmultiprocessing 而不使用 futures。)
我应该在Windows上使用OS线程的原因是什么?
简而言之:如果您坚持使用线程,您几乎可以编写跨平台代码,但如果您转向进程,则实际上正在为两个不同的平台编写代码。
首先,请阅读Programming guidelines中的multiprocessing模块(“所有平台”部分和“Windows”部分)。幸运的是,DB包装器不应遇到大部分问题。您只需要通过ProcessPoolExecutor处理进程。并且,无论您是在游标操作级别还是查询级别上封装东西,所有参数和返回值都将是可以被pickle的简单类型。尽管如此,这仍然是一件您必须小心谨慎的事情,否则将不会成为问题。
与此同时,Windows 的进程内同步对象的开销非常低,但进程间同步对象的开销非常高。(它也有非常快的线程创建和非常慢的进程创建,但如果您使用池,则这不重要。)那么,你如何处理这个问题?我很乐意创建操作系统线程来等待跨进程同步对象并发出信号给 greenlets,但是您对“乐趣”的定义可能会有所不同。
最后,tpool 可以轻松地适应 Unix 下的 ppool,但在 Windows 上需要更多的工作(您需要了解 Windows 才能做到这一点)。

谢谢你的出色回答! 你能解释一下为什么我应该在Windows上使用操作系统线程(“尤其是如果你关心Windows”)吗? - Robert Zaremba
1
@RobertZaremba:我会编辑答案,因为它太长了,无法适应评论。 - abarnert
@RobertZaremba:我知道很多人把这当作某种教条来宣扬,但如果你理解其中的区别,那就是愚蠢的。使用大量线程总是不好的;如果你需要运行1000个任务,请使用greenlets。在Python中使用线程来并行处理CPU密集型任务是不起作用的;如果你需要这样做,请使用multiprocessing。但是当这两者都不相关时,使用greenlets或multiprocessing而不是线程意味着你得到了一些被设计为尽可能像线程一样工作但只能完成90%的工作,而没有实际的好处。只需使用线程即可。 - abarnert

2
abarnert的答案是正确而且非常全面的。我只想补充一点,eventlet中没有“激进”的修补程序,这可能是gevent的特点。此外,如果库使用select,那也不是问题,因为eventlet也可以修补它。
实际上,在大多数情况下,eventlet.monkey_patch()就足够了。当然,在创建任何套接字之前必须执行此操作。
如果您仍然有任何问题,请随时打开问题或写信给eventlet邮件列表或G+社区。所有相关链接都可以在http://eventlet.net/找到。

1
是的,aggressivegevent 特有的。它意味着无法被修补的所有内容都将被删除。因此,如果你只是调用了 monkey.patch_all()(或者 monkey.patch_select()),那么你的第三方库可能会发现,例如,select.epoll 存在,并优先使用该方法而不是 select.select,最终阻塞在 epoll 上;如果你指定了 monkey.patch_all(aggressive=True)select.epoll 将不存在,库将回退到被修补过的 select.select,一切都将正常。 - abarnert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接