如果以下所有条件都属实,您可以假设它将被神奇地修补。
- 您确定 I/O 是建立在标准 Python
socket
或其他eventlet
/gevent
猴子补丁之上的。没有文件,没有本机 (C) 套接字对象等。
- 您传递
aggressive=True
给patch_all
(或patch_select
),或者您确定该库不使用select
或类似的东西。
- 驱动程序不使用任何(隐式)内部线程。(如果驱动程序确实在内部使用线程,
patch_thread
可能会工作,但可能不会。)
如果您不确定,它很容易测试——可能比通读代码并尝试解决它更容易。有一个greenlet,它只是做这样的事情:
while True:
print("running")
gevent.sleep(0.1)
然后让另一个对数据库运行慢查询。如果是monkeypatched,循环greenlet 将保持打印“运行”10 次/秒;如果没有,当程序在查询中被阻塞时,循环的 greenlet 将不会运行。
那么,如果您的驱动程序阻塞,您会怎么做?
最简单的解决方案是使用真正并发的线程池进行数据库查询。这个想法是您将每个查询(或批处理)作为线程池作业启动,并gevent
在该作业完成时阻止您的。(对于非常简单的情况,您不需要很多并发查询,您可以只threading.Thread
为每个查询生成一个,但通常您无法逃脱。)
如果驱动程序做了重要的 CPU 工作(例如,您正在使用运行进程内缓存的东西,甚至是像 sqlite 这样的整个进程内 DBMS),您希望这个线程池实际上是在进程之上实现的,否则GIL 可能会阻止您greenlets
运行。否则(特别是如果您关心 Windows),您可能想要使用操作系统线程。(但是,这意味着您不能patch_threads()
;如果您需要这样做,请使用流程。)
如果您正在使用eventlet
,并且想要使用线程,那么有一个名为的内置简单解决方案tpool
可能就足够了。如果您正在使用gevent
,或者您需要使用流程,这将不起作用。不幸的是,在真正的线程对象上阻塞一个greenlet(不阻塞整个事件循环)在eventlet
and之间有点不同gevent
,并且没有很好地记录,但tpool
源代码应该会给你这个想法。除了那部分之外,其余的只是使用concurrent.futures
(futures
如果您在 2.x 或 3.1 中需要,请参阅 pypi)来执行 aThreadPoolExecutor
或ProcessPoolExecutor
. (或者,如果您愿意,您可以直接使用threading
或multiprocessing
代替使用futures
。)
你能解释一下为什么我应该在 Windows 上使用操作系统线程吗?
简短的总结是:如果你坚持使用线程,你几乎可以只编写跨平台代码,但如果你使用进程,你实际上是在为两个不同的平台编写代码。
首先,阅读模块的编程指南multiprocessing
(“所有平台”部分和“Windows”部分)。幸运的是,DB 包装器不应该遇到这种情况。您只需要通过ProcessPoolExecutor
. 而且,无论您是在游标操作级别还是查询级别进行包装,您的所有参数和返回值都将是可以腌制的简单类型。不过,这是您必须小心的事情,否则不会成为问题。
同时,Windows 的进程内同步对象开销很低,但进程间同步对象开销很大。(它还具有非常快的线程创建和非常慢的进程创建,但如果您使用的是池,这并不重要。)那么,您如何处理呢?我在创建操作系统线程以等待跨进程同步对象并发出信号时获得了很多乐趣,但是您对乐趣的定义可能会有所不同。
最后,tpool
可以很容易地适应ppool
Unix,但在 Windows 上需要更多的工作(你必须了解 Windows 才能完成这项工作)。