сборка мусора общих данных в многопроцессорной обработке через форк - PullRequest
0 голосов
/ 18 октября 2018

Я выполняю некоторую многопроцессорную работу в linux и использую разделяемую память, которая в настоящее время явно не передается дочерним процессам (не через аргумент).

В официальной многопроцессорной обработке python Рекомендации по программированию в разделе «Явно передать ресурсы дочерним процессам» написано:

В Unix, используя метод запуска fork, дочерний процесс может использовать общий ресурс, созданный в родительском процессе.используя глобальный ресурс.Однако лучше передать объект в качестве аргумента в конструктор дочернего процесса .... this ... гарантирует, что пока дочерний процесс еще жив, объект не будет собирать мусор вродительский процесс. Это может быть важно, если какой-то ресурс освобождается, когда объект подвергается сборке мусора в родительском процессе.

мне кажется, что этого объяснения немного не хватает.

  1. Когда я должен беспокоиться о сборке мусора?
  2. Должен ли я всегда передавать данные ребенку, потому что в противном случае иногда будут неожиданные результаты, или это только лучшая практика?

Сейчас я не испытываю какой-либо неожиданной сборки мусораОднако эта ситуация мне кажется ненадежной.

1 Ответ

0 голосов
/ 27 ноября 2018

Это сильно зависит от A) ваших данных и B) вашего многопроцессорного метода .

TLDR:

  • spawn объекты клонируются и каждый завершается в каждом процессе
  • fork / forkserver объекты совместно используются и финализируются в основном процессе

  • Некоторые объекты плохо реагируют на финализацию в основном процессе, хотя все еще используются в дочерних процессах.

  • Документы на args неверны , поскольку содержимое args само по себе не поддерживается (3.7.0)

Примечание: Полный код доступен в виде gist .Весь вывод из CPython 3.7.0 в macOS 10.13.

Мы начнем с простого объекта, который сообщает, где и когда он был завершен:

def print_pid(*args, **kwargs):  # Process aware print helper
    print('[%s]' % os.getpid(), *args, **kwargs)


class Finalisable:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return '<Finalisable object %s at 0x%x>' % (getattr(self, 'name', 'unknown'), id(self))

    def __del__(self):
        print_pid('finalising', self)

Ранний сбор с args

Чтобы проверить, как args работает для GC, мы можем построить процесс и немедленно освободить ссылку на его аргумент:

def drop_early():
    payload = Finalisable()
    child = multiprocessing.Process(target=print, args=(payload,))
    print('drop')
    del payload  # remove sole local reference for `args` content
    print('start')
    child.start()
    child.join()

С помощью метода spawn оригинал собирается, но у дочернего элемента есть свой собственный.копия для завершения:

### test drop_early in 15333 method: spawn
drop
start
[15333] finalising <Finalisable object early at 0x102347390>
[15336] child sees <Finalisable object early at 0x109bd8128>
[15336] finalising <Finalisable object early at 0x109bd8128>
### done

С помощью метода fork оригинал завершается , и ребенок получает этот завершенный объект :

### test drop_early in 15329 method: fork
drop
start
[15329] finalising <Finalisable object early at 0x108b453c8>
[15331] child sees <Finalisable object early at 0x108b453c8>
### done

Это показывает, чтополезная нагрузка основного процесса завершена за до , дочерний процесс запускается и завершается! Итог, args не является защитой от раннего сбора!

Ранний сбор общих объектов

В Python есть некоторые типы, предназначенные для безопасного обмена между процессами.Мы также можем использовать это как маркер:

def drop_early_shared():
    payload = Finalisable(multiprocessing.Value('i', 65))
    child = multiprocessing.Process(target=print_pid, args=('child sees', payload,))
    print('drop')
    del payload
    print('start')
    child.start()
    child.join()

С помощью метода fork, Value собирается рано, но все еще функционально:

### test drop_early_shared in 15516 method: fork
drop
start
[15516] finalising <Finalisable object <Synchronized wrapper for c_int(65)> at 0x1071a3e10>
[15519] child sees <Finalisable object <Synchronized wrapper for c_int(65)> at 0x1071a3e10>
### done

С spawn метод, Value собирается рано и полностью разбит для ребенка:

### test drop_early_shared in 15520 method: spawn
drop
start
[15520] finalising <Finalisable object <Synchronized wrapper for c_int(65)> at 0x103a16c18>
[15524] finalising <Finalisable object unknown at 0x101aa0128>
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/spawn.py", line 105, in spawn_main
    exitcode = _main(fd)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/spawn.py", line 115, in _main
    self = reduction.pickle.load(from_parent)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/synchronize.py", line 111, in __setstate__
    self._semlock = _multiprocessing.SemLock._rebuild(*state)
FileNotFoundError: [Errno 2] No such file or directory
### done

Это показывает, что поведение при финализации зависит от вашего объекта и вашей среды. Итог, не думайте, что ваш объект хорошо себя ведет!


Хотя рекомендуется передавать данные через args, это не освободить основной процесс от его обработки!Объекты могут плохо реагировать на раннее завершение, когда основной процесс отбрасывает ссылки.

Поскольку CPython использует быстродействующий счетчик ссылок, вы заметите вредные эффекты практически сразу.Однако другие реализации, например PyPy, могут скрывать такие побочные эффекты в течение произвольного времени.

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