Глобально модифицируемый объект, общий для нескольких процессов и модулей - PullRequest
0 голосов
/ 26 февраля 2019

Проблема

Я пишу программный продукт, в котором я хотел бы поделиться объектом из определенного модуля.Этот объект должен быть модифицируемым из разных модулей и внутри разных процессов.Рассмотрим следующую (упрощенную) версию проблемы:

Модули

module_shared.py

# Example class with simplified behaviour
class Shared:

    def __init__(self):
        self.shared = dict()

    def set(self, **kwargs):
        for key, value in kwargs.items():
            self.shared[key] = value

    def get(self, *args):
        return {key: self.shared[key] for key in args} if args else self.shared

# Module-scope instance of the Shared class
shared = Shared()

module_a.py

from multiprocessing import Process
from time import sleep
import module_shared as ms

def run():
    Process(target=run_process).start()

def run_process():
    i = 0
    while True:
        sleep(3)
        ms.shared.set(module_a=i)
        i+=1
        print("Shared from within module_a", ms.shared.get())

module_b.py

from multiprocessing import Process
from time import sleep
import module_shared as ms


def run():
    Process(target=run_process).start()

def run_process():
    i = 0
    while True:
        sleep(2)
        ms.shared.set(module_b=i)
        i-=1
        print("Shared from within module_b", ms.shared.get())

module_main.py

import module_a
import module_b
import module_shared as ms
from time import sleep

if __name__ == '__main__':
    module_a.run()
    module_b.run()
    while True:
        sleep(5)
        print("Shared from within module_main", ms.shared.get())

Выход

Результат работы module_main выглядит следующим образом:

Shared from within module_b {'module_b': 0}
Shared from within module_a {'module_a': 0}
Shared from within module_b {'module_b': -1}
Shared from within module_main {}
Shared from within module_a {'module_a': 1}
Shared from within module_b {'module_b': -2}
...

Ожидаемый результат выглядит следующим образом:

Shared from within module_b {'module_b': 0}
Shared from within module_a {'module_a': 0, 'module_b': 0}
Shared from within module_b {'module_a': 0, 'module_b': -1}
Shared from within module_main {'module_a': 0, 'module_b': -1}
Shared from within module_a {'module_a': 1, 'module_b': -1}
Shared from within module_b {'module_a': 1, 'module_b': -2}
...

Дальнейшее объяснение

Экземпляр sharedне изменяется глобально, потому что каждый процесс имеет свое собственное пространство памяти.Первоначально я попытался исправить это с помощью модуля Manager из multiprocessing, однако мне не удалось его настроить, я полагаю, из-за ошибок, связанных с тем, когда и как выполняются операторы импорта.Вот сообщение об ошибке при вызове Manager() в Shared __init__:

RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

На данный момент лучшим решением было использование потоков, однако я бы предпочел вместо этого использовать процессы.Естественно, если существуют более простые (или лучшие) решения, я был бы очень рад их рассмотреть.

EDIT :

Я понял, что сделал опечатку вМоя предыдущая попытка с многопоточностью и использованием нескольких потоков на самом деле работает отлично.Отличный урок, чтобы научиться читать код дважды ...

Ответы [ 2 ]

0 голосов
/ 06 марта 2019

Глядя на документацию для пользовательских Manager объектов, вот идея.

Добавьте эти строки в module_shared.py:

from multiprocessing.managers import BaseManager

class SharedManager(BaseManager):
    pass

SharedManager.register('Shared', Shared)
manager = SharedManager()
manager.start()
shared = manager.Shared()

(Избавьтесьстарого определения shared)

Запуск этого на моем компьютере произвел

$ python module_main.py 
Shared from within module_b {'module_b': 0}
Shared from within module_a {'module_b': 0, 'module_a': 0}
Shared from within module_b {'module_b': -1, 'module_a': 0}
Shared from within module_main {'module_b': -1, 'module_a': 0}
Shared from within module_a {'module_b': -1, 'module_a': 1}
Shared from within module_b {'module_b': -2, 'module_a': 1}
Shared from within module_b {'module_b': -3, 'module_a': 1}
Shared from within module_a {'module_b': -3, 'module_a': 2}
Shared from within module_main {'module_b': -3, 'module_a': 2}
Shared from within module_b {'module_b': -4, 'module_a': 2}
...etc

, который выглядит для меня как ожидаемый результат.

Это немного странно, чтоmodule_shared.py запускает процесс (строка manager.start()), поскольку мы, как правило, не ожидаем, что модули что-либо сделают, но, учитывая ограничения вопроса, я думаю, что это единственный способ сделать это.Если бы я писал это для себя, я бы сделал менеджер в module_main вместо module_shared так же, как мы это делали здесь (возможно, используя менеджер контекста, описанный в ссылке на документацию выше вместо метода .start) иЯ бы передал этого менеджера в качестве аргумента функции методам run a и b.

. Возможно, вас также заинтересует SyncManager, который является подклассом.из BaseManager, который уже зарегистрировал множество базовых типов, включая dict, который в основном охватывает функциональность здесь.

0 голосов
/ 06 марта 2019

Один из подходов заключается в использовании одного из различных модулей кэширования.diskcache, shelve и т. Д. - все они дают возможность сохранять предметы.Конечно, pickle.

Например, используя библиотеку diskcache , вы можете использовать этот подход, заменив module_shared.py на:

### DISKCACHE Example ###
from diskcache import Cache

cache = Cache('test_cache.cache')

# Example class with simplified behaviour
class Shared:

    def __init__(self, cache):
        self.cache = cache
        self.cache.clear()

    def set(self, **kwargs):
        for key, value in kwargs.items():
            cache.set(key, value)

    def get(self, *args):
        return {key: cache.get(key) for key in args} if args else {(key, cache.get(key)) for key in cache.iterkeys()}


# Module-scope instance of the Shared class
shared = Shared(cache)

Output:

Shared from within module_b {('module_b', 0)}
Shared from within module_a {('module_a', 0), ('module_b', 0)}
Shared from within module_b {('module_a', 0), ('module_b', -1)}
Shared from within module_main {('module_a', 0), ('module_b', -1)}
Shared from within module_a {('module_b', -1), ('module_a', 1)}
Shared from within module_b {('module_b', -2), ('module_a', 1)}

В приведенном выше примере module_shared.py является единственным измененным файлом.

Каждая из различных постоянных библиотек / подходов имеет свои особенности и возможности.Если вам абсолютно необходимо сохранить объект экземпляра класса целиком, это уже здесь.:) Производительность зависит только от вашего внедрения и выбора механизма кэширования.diskcache оказался вполне способным для меня.

Я реализовал diskcache очень просто здесь, чтобы продемонстрировать функциональность.Обязательно прочитайте документы, которые являются четкими и краткими, для лучшего понимания.

Кроме того, мой вывод представляет неупорядоченный диктат.Сначала вы можете легко отсортировать отсортированные значения, чтобы они соответствовали вашему собственному выводу с module_a.Я оставил этот бит для простоты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...