我正在使用一个使用LevelDB
并且使用多个长期存在的进程执行不同任务的应用程序。
由于LevelDB只允许一个进程维护数据库连接,因此我们所有的数据库访问都通过特殊的数据库进程进行。
要从另一个进程访问数据库,我们使用BaseProxy
。但是,由于我们使用的是asyncio
,因此我们的代理不应阻塞这些调用db进程的API,这些API最终会从db中读取。因此,我们使用执行程序在代理上实现API。
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
thread_pool_executor,
self._callmethod,
method_name,
args,
)
虽然效果很好,但我想知道是否有更好的替代方法,可以将_callmethod
的{{1}}调用包装在BaseProxy
中。
据我了解,ThreadPoolExecutor
调用数据库进程是等待IO的教科书示例,因此为此使用线程似乎是不必要的浪费。
在理想情况下,我假设BaseProxy
上存在一个async _acallmethod
,但不幸的是该API不存在。
因此,我的问题基本上可以归结为:使用BaseProxy
时,有没有比BaseProxy
中运行这些跨进程调用更有效的选择了?
答案 0 :(得分:5)
不幸的是,多处理库不适合转换为asyncio,如果必须使用BaseProxy
处理IPC(进程间通信),那么您所能做的就是最好的。
虽然该库确实使用了阻塞I / O,但是您在这里不容易进入并重新处理阻塞部分以使用非阻塞原语。如果您坚持采用这种方法,则必须修补或重写该库的内部实现细节,但是作为内部实现细节,这些内容可能会因Python点发行版本而有所不同,从而使任何修补程序均易碎且容易中断Python升级。 _callmethod
方法是涉及线程,套接字或管道连接以及序列化程序的深层抽象层次的一部分。参见multiprocessing/connection.py
和multiprocessing/managers.py
。
因此,您的选择是继续使用当前方法(使用线程池执行程序将BaseProxy._callmethod()
推送到另一个线程) o r,以使用异步原语来实现自己的IPC解决方案。中央数据库访问进程将充当服务器,供其他进程使用套接字或命名管道,并使用针对客户端请求和服务器响应的商定序列化方案,以客户端身份连接到客户端。这是multiprocessing
为您实现的,但是您将使用asyncio
streams和最适合您的应用程序模式的任何序列化方案(例如,pickle,JSON,protobuffers或其他)来实现自己的(简单)版本完全)。
答案 1 :(得分:1)
您需要一个线程池。 aioprocessing提供了一些多处理的异步功能,但是它按照您的建议使用线程来完成。如果没有人公开真正的异步多处理,我建议对python提出一个问题。
https://github.com/dano/aioprocessing
在大多数情况下,该库通过在ThreadPoolExecutor中执行调用来使对多处理方法的阻塞调用异步进行
答案 2 :(得分:0)
假设您的python和数据库在同一系统中运行(即,您不想async
进行任何网络调用),则有两种选择。
您已经在做什么(在执行程序中运行)。它阻止了db线程,但是主线程仍然可以做其他事情。这不是纯粹的非阻塞,但对于I / O阻塞情况而言,这是可以接受的解决方案,并且维护线程的开销很小。
对于真正的非阻塞解决方案(可以在单个线程中运行而不会阻塞),您必须拥有#1。对于每个提取调用,数据库对async
(回调)的本机支持以及#2将其包装在您的自定义事件循环实现中。在这里,您可以对Base循环进行子类化,并覆盖方法以集成数据库回调。例如,您可以创建一个实现管道服务器的基本循环。 db写入管道,而python轮询管道。请参见asyncio
代码库中的Proactor事件循环的实现。注意:我从未实现过任何自定义事件循环。
我对leveldb并不熟悉,但是对于键值存储,尚不清楚这种用于fetch和纯非阻塞实现的回调是否会带来任何明显的好处。如果您在一个迭代器中遇到多个访存问题,而这是您的主要问题,则可以进行循环async
(每个访存仍处于阻塞状态)并提高性能。下面是解释此问题的虚拟代码。
import asyncio
import random
import time
async def talk_to_db(d):
"""
blocking db iteration. sleep is the fetch function.
"""
for k, v in d.items():
time.sleep(1)
yield (f"{k}:{v}")
async def talk_to_db_async(d):
"""
real non-blocking db iteration. fetch (sleep) is native async here
"""
for k, v in d.items():
await asyncio.sleep(1)
yield (f"{k}:{v}")
async def talk_to_db_async_loop(d):
"""
semi-non-blocking db iteration. fetch is blocking, but the
loop is not.
"""
for k, v in d.items():
time.sleep(1)
yield (f"{k}:{v}")
await asyncio.sleep(0)
async def db_call_wrapper(db):
async for row in talk_to_db(db):
print(row)
async def db_call_wrapper_async(db):
async for row in talk_to_db_async(db):
print(row)
async def db_call_wrapper_async_loop(db):
async for row in talk_to_db_async_loop(db):
print(row)
async def func(i):
await asyncio.sleep(5)
print(f"done with {i}")
database = {i:random.randint(1,20) for i in range(20)}
async def main():
db_coro = db_call_wrapper(database)
coros = [func(i) for i in range(20)]
coros.append(db_coro)
await asyncio.gather(*coros)
async def main_async():
db_coro = db_call_wrapper_async(database)
coros = [func(i) for i in range(20)]
coros.append(db_coro)
await asyncio.gather(*coros)
async def main_async_loop():
db_coro = db_call_wrapper_async_loop(database)
coros = [func(i) for i in range(20)]
coros.append(db_coro)
await asyncio.gather(*coros)
# run the blocking db iteration
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# run the non-blocking db iteration
loop = asyncio.get_event_loop()
loop.run_until_complete(main_async())
# run the non-blocking (loop only) db iteration
loop = asyncio.get_event_loop()
loop.run_until_complete(main_async_loop())
这是您可以尝试的方法。否则,我会说您当前的方法非常有效。我认为BaseProxy无法为您提供异步acall API,它不知道如何处理数据库中的回调。