Ответ 1
Надеюсь, что это хорошо, чтобы записать мои заметки об этой проблеме.
Прежде всего, я очень ценю пример в OP, потому что именно там я и начал - хотя это заставило меня думать, что shared
- это какой-то встроенный модуль Python, пока я не нашел полный пример в [Tutor] Глобальные переменные между модулями?.
Однако, когда я искал "совместное использование переменных между скриптами" (или процессов) - помимо случая, когда Python script должен использовать переменные, определенные в других исходных файлах Python (но не обязательно запущенные процессы) - я в основном споткнулся в двух других случаях использования:
- A script разворачивается в несколько дочерних процессов, которые затем запускаются параллельно (возможно, на нескольких процессорах) на одном ПК
- A script генерирует несколько других дочерних процессов, которые затем запускаются параллельно (возможно, на нескольких процессорах) на одном ПК
Таким образом, большинство обращений относительно "общих переменных" и "межпроцессного общения" (IPC) обсуждают такие случаи, как эти два; однако в обоих случаях можно наблюдать "родителя", к которому "дети" обычно имеют ссылку.
В то же время я заинтересован в нескольких вызовах одного и того же script, запускался независимо и обменивался данными между ними (как в Python: как разделить объект экземпляр через несколько вызовов script) в режиме singleton/single instance. Эта проблема на самом деле не устранена вышеупомянутыми двумя случаями - вместо этого она существенно сводится к примеру в OP (обмен переменными через два сценария).
Теперь, имея дело с этой проблемой в Perl, есть IPC::Shareable; который "позволяет привязать переменную к общей памяти", используя "целое число или 4 символьную строку [1], которая служит общим идентификатором для данных в пространстве процесса". Таким образом, нет временных файлов, а также сетевых настроек, которые я считаю отличными для моего варианта использования; поэтому я искал то же самое в Python.
Однако, как принятый ответ от @Drewfer отмечает: "Вы не сможете делать то, что хотите, не сохраняя информацию где-то внешнюю по отношению к двум экземпляры переводчика"; или другими словами: либо вы должны использовать настройку сети/сокета, либо вам нужно использовать временные файлы (ergo, нет общей RAM для "полностью отдельных сеансов python" ).
Теперь даже с этими соображениями трудно найти рабочие примеры (кроме pickle
) - также в документах для mmap и multiprocessing. Мне удалось найти другие примеры, которые также описывают некоторые подводные камни, о которых не говорится в документах:
- Использование
mmap
: рабочий код в двух разных сценариях в Обмен данными Python между процессами с помощью mmap | блог schmichael- Демонстрирует, как оба сценария изменяют общее значение
- Обратите внимание, что здесь временный файл создается как хранилище для сохраненных данных.
mmap
- это просто специальный интерфейс для доступа к этому временному файлу.
- Использование
multiprocessing
: рабочий код по адресу:- Python многопроцессорный RemoteManager под многопроцессорным процессом. Процесс - рабочий пример
SyncManager
(черезmanager.start()
) с общимQueue
; сервер пишет, клиенты читают (общие данные) - Сравнение модуля многопроцессорности и pyro? - рабочий пример
BaseManager
(черезserver.serve_forever()
) с общим пользовательским классом; сервер пишет, клиент читает и пишет - Как синхронизировать python dict с многопроцессорностью - этот ответ имеет большое объяснение ошибок
multiprocessing
и является рабочим примеромSyncManager
(черезmanager.start()
) с общим dict; сервер ничего не делает, клиент читает и пишет
- Python многопроцессорный RemoteManager под многопроцессорным процессом. Процесс - рабочий пример
Благодаря этим примерам я привел пример, который по существу делает то же самое, что и пример mmap
, с подходами из примера "синхронизировать python dict" - используя BaseManager
(через manager.start()
через файл адрес пути) с общим списком; как для чтения, так и для чтения и записи на сервере и клиенте (вставка ниже). Обратите внимание:
- Менеджеры
-
multiprocessing
могут запускаться либо черезmanager.start()
, либоserver.serve_forever()
-
serve_forever()
locks -start()
не - В
multiprocessing
есть средство автозапуска: он отлично работает с процессамиstart()
ed, но, похоже, игнорирует те, чтоserve_forever()
-
- Спецификация адреса в
multiprocessing
может быть IP (сокет) или временным файлом (возможно, с каналом?); вmultiprocessing
docs:- В большинстве примеров используется
multiprocessing.Manager()
- это просто функция (не экземпляр класса), которая возвращаетSyncManager
, который является специальным подклассомBaseManager
; и используетstart()
- но не для IPC между независимо запущенными скриптами; здесь используется путь к файлу - Несколько других примеров
serve_forever()
подход для IPC между сценариями с независимым запуском; здесь используется IP/адрес сокета - Если адрес не указан, то автоматически используется путь к временному файлу (см. 16.6.2.12. Регистрация для примера того, как см. это)
- В большинстве примеров используется
В дополнение ко всем ловушкам в записи "synchronize a python dict" в случае списка есть дополнительные. Это сообщение отмечает:
Все манипуляции с dict должны выполняться с помощью методов, а не диктовых назначений (syncdict [ "blast" ] = 2 терпит неудачу из-за того, что многопроцессорность делится пользовательскими объектами)
Обходным путем для dict['key']
получения и настройки является использование открытых методов dict
get
и update
. Проблема в том, что нет таких общедоступных методов, как альтернатива для list[index]
; таким образом, для общего списка, кроме того, мы должны регистрировать методы __getitem__
и __setitem__
(которые являются частными для list
) как exposed
, что означает, что нам также необходимо перерегистрировать все общедоступные методы для list
, а также :/
Ну, я думаю, что это были самые важные вещи; это два сценария - их можно просто запустить в отдельных терминалах (сначала сервер); примечание, разработанное на Linux с Python 2.7:
a.py
(сервер):
import multiprocessing
import multiprocessing.managers
import logging
logger = multiprocessing.log_to_stderr()
logger.setLevel(logging.INFO)
class MyListManager(multiprocessing.managers.BaseManager):
pass
syncarr = []
def get_arr():
return syncarr
def main():
# print dir([]) # cannot do `exposed = dir([])`!! manually:
MyListManager.register("syncarr", get_arr, exposed=['__getitem__', '__setitem__', '__str__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'])
manager = MyListManager(address=('/tmp/mypipe'), authkey='')
manager.start()
# we don't use the same name as `syncarr` here (although we could);
# just to see that `syncarr_tmp` is actually <AutoProxy[syncarr] object>
# so we also have to expose `__str__` method in order to print its list values!
syncarr_tmp = manager.syncarr()
print("syncarr (master):", syncarr, "syncarr_tmp:", syncarr_tmp)
print("syncarr initial:", syncarr_tmp.__str__())
syncarr_tmp.append(140)
syncarr_tmp.append("hello")
print("syncarr set:", str(syncarr_tmp))
raw_input('Now run b.py and press ENTER')
print
print 'Changing [0]'
syncarr_tmp.__setitem__(0, 250)
print 'Changing [1]'
syncarr_tmp.__setitem__(1, "foo")
new_i = raw_input('Enter a new int value for [0]: ')
syncarr_tmp.__setitem__(0, int(new_i))
raw_input("Press any key (NOT Ctrl-C!) to kill server (but kill client first)".center(50, "-"))
manager.shutdown()
if __name__ == '__main__':
main()
b.py
(клиент)
import time
import multiprocessing
import multiprocessing.managers
import logging
logger = multiprocessing.log_to_stderr()
logger.setLevel(logging.INFO)
class MyListManager(multiprocessing.managers.BaseManager):
pass
MyListManager.register("syncarr")
def main():
manager = MyListManager(address=('/tmp/mypipe'), authkey='')
manager.connect()
syncarr = manager.syncarr()
print "arr = %s" % (dir(syncarr))
# note here we need not bother with __str__
# syncarr can be printed as a list without a problem:
print "List at start:", syncarr
print "Changing from client"
syncarr.append(30)
print "List now:", syncarr
o0 = None
o1 = None
while 1:
new_0 = syncarr.__getitem__(0) # syncarr[0]
new_1 = syncarr.__getitem__(1) # syncarr[1]
if o0 != new_0 or o1 != new_1:
print 'o0: %s => %s' % (str(o0), str(new_0))
print 'o1: %s => %s' % (str(o1), str(new_1))
print "List is:", syncarr
print 'Press Ctrl-C to exit'
o0 = new_0
o1 = new_1
time.sleep(1)
if __name__ == '__main__':
main()
Как последнее замечание, в Linux /tmp/mypipe
создан - но он равен 0 байтам и имеет атрибуты srwxr-xr-x
(для сокета); Я думаю, это радует меня, так как мне не нужно беспокоиться о сетевых портах, а также о временных файлах как таковых :)
Другие связанные вопросы: