Надеюсь, можно записать мои заметки об этой проблеме здесь.
Прежде всего, я очень ценю пример в OP, потому что именно с этого я и начал - хотя он заставил меня думать, shared
- это какой-то встроенный модуль Python, пока я не нашел полный пример в [Tutor] Глобальные переменные между модулями ?? .
Однако, когда я искал «совместное использование переменных между сценариями» (или процессами) - кроме случая, когда сценарию Python необходимо использовать переменные, определенные в других исходных файлах Python (но не обязательно при запуске процессов) - я в основном наткнулся на два другие случаи использования:
- Сценарий разветвляется на несколько дочерних процессов, которые затем выполняются параллельно (возможно, на нескольких процессорах) на одном компьютере
- Сценарий порождает несколько других дочерних процессов, которые затем выполняются параллельно (возможно, на нескольких процессорах) на одном компьютере
Как таковые, большинство обращений, касающихся «общих переменных» и «межпроцессного взаимодействия» (IPC), обсуждают случаи, подобные этим двум; однако в обоих этих случаях можно наблюдать «родителя», на который обычно ссылаются «дети».
Однако меня интересует выполнение нескольких вызовов одного и того же сценария, выполняемых независимо, и совместное использование данных между ними (как в Python: как разделить экземпляр объекта между несколькими вызовами сценария ), в режиме одного экземпляра. Эта проблема на самом деле не решается в двух вышеупомянутых случаях - вместо этого она сводится к примеру в OP (разделение переменных между двумя сценариями).
Теперь при решении этой проблемы в Perl существует IPC :: Shareable ; который «позволяет привязать переменную к общей памяти», используя «целое число или строку из 4 символов [1], которая служит общим идентификатором для данных в пространстве процесса». Таким образом, нет ни временных файлов, ни сетевых настроек - что я считаю отличным для моего варианта использования; поэтому я искал то же самое в Python.
Однако, как принял ответ от @ Drewfer отмечает: " Вы не сможете делать то, что хотите, без хранения информации где-то за пределами двух экземпляров интерпретатора"; или другими словами: либо вы должны использовать настройку сети / сокета, либо вы должны использовать временные файлы (следовательно, нет общей оперативной памяти для " полностью отдельных сеансов Python ").
Теперь, даже с учетом этих соображений, довольно трудно найти рабочие примеры (за исключением pickle
) - также в документах для mmap и многопроцессорной обработки . Мне удалось найти некоторые другие примеры, которые также описывают некоторые подводные камни, которые не упоминаются в документации:
- Использование
mmap
: рабочий код в двух разных скриптах на Обмен данными Python между процессами с использованием mmap | блог schmichael
- Демонстрирует, как оба сценария изменяют общее значение
- Обратите внимание, что здесь временный файл создается как хранилище для сохраненных данных -
mmap
- это просто специальный интерфейс для доступа к этому временному файлу
- Использование
multiprocessing
: рабочий код по адресу:
Благодаря этим примерам я придумал пример, который, по сути, делает то же самое, что и пример mmap
, с подходами из примера " синхронизируют python dict " - с использованием BaseManager
( через manager.start()
через адрес пути к файлу) с общим списком; и сервер, и клиент читают и пишут (вставлено ниже). Обратите внимание, что:
multiprocessing
менеджеры могут быть запущены через manager.start()
или server.serve_forever()
serve_forever()
замки - start()
не
- В
multiprocessing
есть функция автоматической регистрации: кажется, что она отлично работает с start()
редактируемыми процессами - но, похоже, игнорирует те, которые serve_forever()
- Спецификация адреса в
multiprocessing
может быть IP-адресом (сокетом) или временным файлом (возможно, каналом?); в multiprocessing
документах:
- В большинстве примеров используется
multiprocessing.Manager()
- это просто функция (, а не экземпляр класса), которая возвращает SyncManager
, который является специальным подклассом BaseManager
; и использует start()
- но не для IPC между независимо запускаемыми сценариями; здесь используется путь к файлу
- Несколько других примеров
serve_forever()
подход для IPC между независимо выполняемыми сценариями; здесь используется IP / адрес сокета
- Если адрес не указан, то путь к временному файлу используется автоматически (см. 16.6.2.12. Ведение журнала для примера того, как это увидеть)
В дополнение ко всем подводным камням в посте " синхронизация python dict ", есть и дополнительные в случае списка. Этот пост отмечает:
Все манипуляции с 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
(для сокета); Я думаю, это меня радует, так как мне не нужно беспокоиться ни о сетевых портах, ни о временных файлах как таковых :)
Другие связанные вопросы: