簡體   English   中英

我可以使用多處理 BaseManager 動態注冊對象以進行代理嗎?

[英]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())

在這里, UpdateServerUpdateManager添加了對方法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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM