![](/img/trans.png)
[英]How do I change the serializer that my multiprocessing.mangers.BaseManager subclass uses to cPickle?
[英]Can I dynamically register objects to proxy with a multiprocessing BaseManager?
有很多使用多處理 BaseManager 派生的 class 來注冊返回隊列句柄代理的方法的示例,然后客戶端可以使用該方法從隊列中提取/放入。
這很好,但我有一個不同的場景 - 如果我需要代理的隊列數量發生變化以響應外部事件怎么辦? 我真正想要的是代理一個返回給定UID的特定隊列的方法。
我試過了,但我無法讓它工作,似乎唯一可用的東西是在 object 被實例化之前在 class 注冊的東西。 一旦我已經實例化了該 class 的實例並使其運行,我就無法BaseManager.register("my-new-queue", lambda: queue.Queue)
。
有沒有辦法解決? 我覺得我們應該能夠動態處理這個
在實際調用可調用對象的“服務器”進程中,注冊是最重要的。 在“客戶端”進程中注冊可調用對象只會將該類型標識(您傳遞給注冊的字符串)作為方法添加到管理器 class。 問題是運行服務器阻塞,阻止您注冊新的可調用對象,並且它發生在另一個進程中,使得修改注冊表更加困難。
我一直在修補這個問題......imao managers
被詛咒了......我認為你之前的問題也會被解決它的東西回答(除了我們在評論中的討論)。 基本上 python 試圖在不發送代理對象的authkey
參數方面有點安全,但它有時會絆倒(尤其是嵌套代理)。 修復方法是為進程mp.current_process().authkey = b'abracadabra'
設置默認的 authkey,當authkey=None
時用作后備( https://bugs.python.org/issue7503 )
這是我的完整測試腳本,它源自docs中的遠程管理器示例。 基本上我創建了一個共享字典來保存共享隊列:
#server process
from multiprocessing.managers import BaseManager, DictProxy
from multiprocessing import current_process
from queue import Queue
queues = {} #dict[uuid, Queue]
class QueueManager(BaseManager):
pass
QueueManager.register('new_queue', callable=Queue)
QueueManager.register('get_queues', callable=lambda:queues, proxytype=DictProxy)
m = QueueManager(address=('localhost', 50000), authkey=b'abracadabra')
current_process().authkey = b'abracadabra'
s = m.get_server()
s.serve_forever()
#process A
from multiprocessing.managers import BaseManager
from multiprocessing import current_process
class QueueManager(BaseManager):
pass
QueueManager.register('new_queue')
QueueManager.register('get_queues')
m = QueueManager(address=('localhost', 50000), authkey=b'abracadabra')
m.connect()
current_process().authkey = b'abracadabra'
queues_dict = m.get_queues()
queues_dict['my_uuid'] = m.new_queue()
queues_dict['my_uuid'].put("this is a test")
#process B
from multiprocessing.managers import BaseManager
from multiprocessing import current_process
class QueueManager(BaseManager):
pass
QueueManager.register('new_queue')
QueueManager.register('get_queues')
m = QueueManager(address=('localhost', 50000), authkey=b'abracadabra')
m.connect()
current_process().authkey = b'abracadabra'
queues_dict = m.get_queues()
print(queues_dict['my_uuid'].get())
關於評論:“ get_queue
獲取 UUID 並返回特定隊列”修改很簡單,並且不涉及嵌套代理,從而避免了摘要身份驗證問題:
#server process
from multiprocessing.managers import BaseManager
from collections import defaultdict
from queue import Queue
queues = defaultdict(Queue)
class QueueManager(BaseManager): pass
QueueManager.register('get_queue', callable=lambda uuid:queues[uuid])
m = QueueManager(address=('localhost', 50000), authkey=b'abracadabra')
s = m.get_server()
s.serve_forever()
#process A
from multiprocessing.managers import BaseManager
class QueueManager(BaseManager): pass
QueueManager.register('get_queue')
m = QueueManager(address=('localhost', 50000), authkey=b'abracadabra')
m.connect()
m.get_queue("my_uuid").put("this is a test")
#process B
from multiprocessing.managers import BaseManager
class QueueManager(BaseManager): pass
QueueManager.register('get_queue')
m = QueueManager(address=('localhost', 50000), authkey=b'abracadabra')
m.connect()
print(m.get_queue("my_uuid").get())
Aaron 的回答可能是這里最簡單的方法,您可以共享一個字典並將隊列存儲在該共享字典中。 但是,它不能解決管理器啟動后無法更新方法的問題。 因此,這是一個更完整的解決方案,比它的替代方案更簡潔,即使在服務器啟動后您也可以更新注冊表:
from queue import Queue
from multiprocessing.managers import SyncManager, Server, State, dispatch
from multiprocessing.context import ProcessError
class UpdateServer(Server):
public = ['shutdown', 'create', 'accept_connection', 'get_methods',
'debug_info', 'number_of_objects', 'dummy', 'incref', 'decref', 'update_registry']
def update_registry(self, c, registry):
with self.mutex:
self.registry.update(registry)
def get_server(self):
if self._state.value != State.INITIAL:
if self._state.value == State.STARTED:
raise ProcessError("Already started server")
elif self._state.value == State.SHUTDOWN:
raise ProcessError("Manager has shut down")
else:
raise ProcessError(
"Unknown state {!r}".format(self._state.value))
return self._Server(self._registry, self._address,
self._authkey, self._serializer)
class UpdateManager(SyncManager):
_Server = UpdateServer
def update_registry(self):
assert self._state.value == State.STARTED, 'server not yet started'
conn = self._Client(self._address, authkey=self._authkey)
try:
dispatch(conn, None, 'update_registry', (type(self)._registry, ), {})
finally:
conn.close()
class MyQueue:
def __init__(self):
self.initialized = False
self.q = None
def initialize(self):
self.q = Queue()
def __call__(self):
if not self.initialized:
self.initialize()
self.initialized = True
return self.q
if __name__ == '__main__':
# Create an object of wrapper class, note that we do not initialize the queue right away (it's unpicklable)
queue = MyQueue()
manager = UpdateManager()
manager.start()
# If you register new typeids, then call update_registry. The method_to_typeid parameter maps the __call__ method to
# return a proxy of the queue instead since Queues are not picklable
UpdateManager.register('uuid', queue, method_to_typeid={'__call__': 'Queue'})
manager.update_registry()
# Once the object is stored in the manager process, now we can safely initialize the queue and share
# it among processes. Initialization is implicit when we call uuid() if it's not already initialized
q = manager.uuid()
q.put('bye')
print(q.get())
在這里, UpdateServer
和UpdateManager
添加了對方法update_registry
的支持,該方法會通知服務器是否向管理器注冊了任何新的 typeid。 MyQueue
只是一個包裝器 class 如果直接調用則返回注冊的新隊列。 雖然它在功能上類似於注冊lambda: queue
,但包裝器是必要的,因為 lamdba 函數不可提取並且服務器進程正在這里的新進程中啟動(而不是在主進程中執行server.serve_forever()
,但您可以執行如果您願意,也可以這樣做)。
因此,即使在管理器進程運行后,您現在也可以注冊 typeid,只需確保在之后立即調用update_registry
function。 如果您在主進程本身中啟動服務器(通過使用serve_forever
,就像在 Aaron 的回答中一樣)並使用manager.connect
從另一個進程連接到它,這個 function 調用甚至可以工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.