синхронизация операций чтения/записи для двух общих объектов в python

#python #concurrency #multiprocessing #subprocess

Вопрос:

Предположим, я использую a multiprocessing.Manager для создания двух общих объектов и передачи их подпроцессу, который начинает запись в эти объекты:

 manager = multiprocessing.Manager()
a = manager.list()
b = manager.list()
subprocess = MyProcess(a,b)
subprocess.start()  # writing to a and b starts here

# inspect a and b - in what order will changes appear?
 

Есть ли способ убедиться, что порядок, в котором эти записи отражаются в родительском процессе, совпадает с порядком, в котором записи выполняются в подпроцессе? Что делать, если я register() класс с двумя членами, и подпроцесс вносит изменения в эти два члена?

Есть ли ссылка, которая отвечает на эти вопросы «порядка операций» в более общем плане? (Я не смог найти это в документах). Например: что, если мы создадим второй подпроцесс subprocess_2 , который также будет выполнять некоторую запись и чтение a , и b — что мы можем сказать об изменениях порядка, которые будут отражены в родительском и в subprocess_2 ?

ПРОСТОЙ, ПОДРОБНЫЙ ПРИМЕР: Следуя примеру Booboo, если myProcess (подкласс multiprocessing.Process ) реализует run() так:

 def run(self) :
    a.append(1)
    b.append(2)
 

затем, если мы подождем достаточно долго, мы знаем, что родительский процесс увидит a == [1] и b == [2] . Вопрос в том, какие возможные состояния мы можем видеть между ними. Если в менеджере есть какая-то глобальная синхронизация, мы сможем увидеть только следующие пары значений для a,b : [],[] , [1],[] или конечного состояния [1],[2] . Но без такой синхронизации мы можем мельком [],[2] увидеть (если, например, сообщение о добавлении к b достиг родителя быстрее, или порядок опроса очередей не такой, как мы ожидаем (чего мы ожидаем?)). Я надеялся, что мне не придется просматривать исходный код (который также может измениться в будущих версиях), а скорее получить общую гарантию, если она есть. Надеюсь, это прояснит вопрос.

Комментарии:

1. Я понимаю, что, возможно, это не лучший ответ, но многопоточность и особенно синхронизация, обмен данными между потоками не являются самыми сильными сторонами Python. Вы выбираете правильный инструмент для этой цели?

2. Основная вычислительная работа в моем приложении выполняется на Java, и я думаю, что именно там я привык получать точные гарантии порядка операций (предоставляемые «моделью памяти java»). Оболочка python делает что — то довольно простое. В python я использую параллелизм только как способ получить большую гибкость в дизайне.

Ответ №1:

В показанном примере вы имеете дело с управляемым списком. Этот список существует в процессе, который создается при выполнении manager = multiprocessing.Manager() . Переменные a и b на самом деле являются прокси-объектами. Когда методы вызываются на этих прокси-серверах, выполняется вызов удаленного метода (механизм связи представляет собой сокет под Linux и именованный канал под Windows) из адресного пространства одного процесса в адресное пространство SynchManager ( multiprocessing.SyncManager будучи классом, который создается вызовом multiprocessing.Manager() ), и фактический метод выполняется потоком, запущенным в адресном пространстве SyncManager, прослушивающим соединение сокета, и не будет отвечать на соединение (возвращаться обратно вызывающему), пока вызов метода не будет завершен.

Обновить

Основываясь на обновленном вопросе OP, возможные состояния списков a и b расположены в порядке времени:

  1. a -> [], b -> []
  2. a -> [1], b -> []
  3. a -> [1], b -> [2]

Других возможных состояний нет, потому что вызов a.append(1) будет заблокирован до тех a пор, пока список не будет дополнен значением 1 . Представьте себе следующий код:

 a.append(1)
# How can the following assertion fail?
# Who would implement a list in such a way where this could fail?
assert(1 in a)
b.append(b)
 

Комментарии:

1. когда я это сделаю a.append(1) , b.append(2) я ожидаю, что по прошествии достаточно долгого времени найду a == [1] и b == [2] , возможно, вы захотите исправить это в своем ответе (вы написали «…список теперь будет [1,2] «), Но далеко не очевидно, что произойдет, если пройдет короткое время. Если есть какая-то глобальная блокировка объектов диспетчера, я ожидаю , что единственными возможными видимыми состояниями будут [],[] [1],[] или [1],[2] . Но в целом, я вполне могу мельком [],[2] увидеть . Надеюсь, это прояснит вопрос.

2. Я хотел сказать a,append(2) (не b.append(2) ) в первом абзаце-я отредактировал ответ). Вот почему я сказал: «Список теперь будет [1, 2] «. Поэтому во втором абзаце моего ответа я просто имел дело с тем, как один из списков. например, список a , может закончиться предположением, что оба процесса записывают 1 и 2 a в таком порядке. Так что, пожалуйста, забудь об b.append(2) этом . Я не делал никаких предположений о том, что записывается во второй список. Вы не показали, как процесс вносит свои изменения в два списка. Например, они чередуются или нет?

3. Теперь я добавил разъяснение к вопросу, используя ваш пример

4. Я посмотрю.

5. и в качестве прекрасного примера состояния гонки, это ваш пример «предварительного редактирования», в котором мы пишем как a, так и b 🙂