我一般都知道,不应该在多进程之间共享对象以及可能由此产生的问题。但我的要求是这样做是必要的。
我有一个复杂的对象,其中包含所有漂亮的协同程序async-await。 一个函数,它在自己的单独进程中对此对象运行长时间运行的进程。现在,我想在主进程中运行一个IPython shell,并对这个复杂的对象进行操作,而长时间运行的进程正在另一个进程中运行。
为了跨进程共享这个复杂的对象,我尝试了在SO上遇到的多处理BaseManager方法:
import multiprocessing
import multiprocessing.managers as m
class MyManager(m.BaseManager):
pass
MyManager.register('complex_asynio_based_class', complex_asynio_based_class)
manager = MyManager()
manager.start()
c = manager.complex_asynio_based_class()
process = multiprocessing.Process(
target=long_running_process,
args=(c,),
)
但这会产生错误:
Unserializable message: Traceback (most recent call last):
File "/usr/3.6/lib/python3.6/multiprocessing/managers.py", line 283, in serve_client
send(msg)
File "/usr/3.6/lib/python3.6/multiprocessing/connection.py", line 206, in send
self._send_bytes(_ForkingPickler.dumps(obj))
File "/usr/3.6/lib/python3.6/multiprocessing/reduction.py", line 51, in dumps
cls(buf, protocol).dump(obj)
TypeError: can't pickle coroutine objects
由于对象中存在协同程序,因此无法正常工作。我无法想出一个更好的解决方案来让它发挥作用,我坚持下去。
如果不是Python,我会为长时间运行的过程生成一个线程,但仍然可以对它进行操作。
如果我没有错,那么这应该是多进程应用程序运行后台进程的常用模式,以及只对它执行一些只读操作的主进程,就像我的情况一样,而不是修改它。我想知道它是如何完成的?
如何在多进程中共享无法挑选的复杂对象?
答案 0 :(得分:5)
运行协程不能在进程间自动共享,因为协程在拥有异步类的进程中的特定事件循环内运行。协程具有无法腌制的状态,即使可以,它在事件循环的上下文之外也没有意义。
你可以做的是为异步类创建一个基于回调的适配器,每个协程方法由一个基于回调的方法表示,语义为"开始执行X并在完成后调用此函数&#34 ;。如果回调是多处理感知的,则可以从其他进程调用这些操作。然后,您可以在每个进程中启动一个事件循环,并在代理的基于回调的调用上创建一个协程外观。
例如,考虑一个简单的异步类:
class Async:
async def repeat(self, n, s):
for i in range(n):
print(s, i, os.getpid())
await asyncio.sleep(.2)
return s
基于回调的适配器可以使用公共asyncio
API将repeat
协同程序转换为JavaScript中的经典异步函数"回调地狱"式:
class CallbackAdapter:
def repeat_start(self, n, s, on_success):
fut = asyncio.run_coroutine_threadsafe(
self._async.repeat(n, s), self._loop)
# Once the coroutine is done, notify the caller.
fut.add_done_callback(lambda _f: on_success(fut.result()))
(转换可以自动进行,上面手动编写的代码只显示了这个概念。)
CallbackAdapter
可以通过多处理注册,因此不同的进程可以通过多处理提供的代理启动适配器的方法(因此也就是原始的异步协同程序)。这只要求作为on_success
传递的回调是多处理友好的。
作为最后一步,可以完全循环并为基于回调的API(!)创建异步适配器,在其他进程中启动事件循环,并使用asyncio和{{1} }。这个适配器适配器类将运行一个功能齐全的async def
协程,它可以有效地代理原始的repeat
协程,而不会试图腌制协程状态。
以下是上述方法的示例实现:
Async.repeat
代码在Python 3.5上正确运行,但由于a bug in multiprocessing而在3.6和3.7上失败。
答案 1 :(得分:0)
I have been using multiprocessing module and asyncio module for a litte while.
You don't share objects between processes. You create an object(referent) in one process, return a proxy object and share it with other process. Other process use proxy object to invoke referent's methods.
In your code, the referent is the complex_asynio_based_class instance.
Here is the stupid code you can refer. THe main thread is a single asyncio loop running UDP server and severl other async ops. The long running process simple checks the loop status.
import multiprocessing
import multiprocessing.managers as m
import asyncio
import logging
import time
logging.basicConfig(filename="main.log", level=logging.DEBUG)
class MyManager(m.BaseManager):
pass
class sinkServer(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
def datagram_received(self, data, addr):
message = data.decode()
logging.info('Data received: {!r}'.format(message))
class complex_asynio_based_class:
def __init__(self, addr=('127.0.0.1', '8080')):
self.loop = asyncio.new_event_loop()
listen = self.loop.create_datagram_endpoint(sinkServer, local_addr=addr,
reuse_address=True, reuse_port=True)
self.loop.run_until_complete(listen)
for name, delay in zip("abcdef", (1,2,3,4,5,6)):
self.loop.run_until_complete(self.slow_op(name, delay))
def run(self):
self.loop.run_forever()
def stop(self):
self.loop.stop()
def is_running(self):
return self.loop.is_running()
async def slow_op(self, name, delay):
logging.info("my name: {}".format(name))
asyncio.sleep(delay)
def long_running_process(co):
logging.debug('address: {!r}'.format(co))
logging.debug("status: {}".format(co.is_running()))
time.sleep(6)
logging.debug("status: {}".format(co.is_running()))
MyManager.register('complex_asynio_based_class', complex_asynio_based_class)
manager = MyManager()
manager.start()
c = manager.complex_asynio_based_class()
process = multiprocessing.Process(
target=long_running_process,
args=(c,),
)
process.start()
c.run() #run the loop