如果以下所有条件都成立,您可以假设它将被自动修补。
- 您确定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(而不阻塞整个事件循环)在
eventlet
和
gevent
中略有不同,并且没有很好的文档记录,但是
tpool
源代码应该能给您提供思路。除此之外,其余部分只需使用
concurrent.futures
(如果您需要在 2.x 或 3.1 中使用,请参见 pypi 上的
futures
)在
ThreadPoolExecutor
或
ProcessPoolExecutor
上执行任务即可。(或者,如果您喜欢,可以直接使用
threading
或
multiprocessing
而不使用
futures
。)
我应该在Windows上使用OS线程的原因是什么?
简而言之:如果您坚持使用线程,您几乎可以编写跨平台代码,但如果您转向进程,则实际上正在为两个不同的平台编写代码。
首先,请阅读
Programming guidelines中的
multiprocessing
模块(“所有平台”部分和“Windows”部分)。幸运的是,DB包装器不应遇到大部分问题。您只需要通过
ProcessPoolExecutor
处理进程。并且,无论您是在游标操作级别还是查询级别上封装东西,所有参数和返回值都将是可以被pickle的简单类型。尽管如此,这仍然是一件您必须小心谨慎的事情,否则将不会成为问题。
与此同时,Windows 的进程内同步对象的开销非常低,但进程间同步对象的开销非常高。(它也有非常快的线程创建和非常慢的进程创建,但如果您使用池,则这不重要。)那么,你如何处理这个问题?我很乐意创建操作系统线程来等待跨进程同步对象并发出信号给 greenlets,但是您对“乐趣”的定义可能会有所不同。
最后,
tpool
可以轻松地适应 Unix 下的
ppool
,但在 Windows 上需要更多的工作(您需要了解 Windows 才能做到这一点)。