Два процесса подключены к одному объекту multiprocessing.shared_memory.SharedMemory. Почему запись одного процесса становится видна другому?
SharedMemory создаёт именованный участок физически общей памяти, к которому каждый процесс подключает собственное виртуальное отображение. Поэтому запись в буфер одного процесса изменяет общие страницы памяти и становится доступна другому процессу без передачи объекта через Queue или Pipe.
Это обеспечивает совместное хранение байтов, но не синхронизацию доступа. При одновременной записи процессов нужны отдельные примитивы координации.
Процессы Python имеют изолированные адресные пространства. Такое разделение повышает надёжность и позволяет обходить ограничения GIL, но обычные Python-объекты одного процесса напрямую другому недоступны.
Для обмена данными обычно применяют очереди, каналы или Pipe. Они удобны, однако требуют сериализации и часто копирования данных. Shared memory появилась как механизм для эффективного обмена большими массивами и буферами без такого промежуточного копирования.
Если передать большой массив через multiprocessing.Queue, его содержимое обычно сериализуется, передаётся между процессами и восстанавливается на стороне получателя. Это увеличивает задержку, потребление памяти и нагрузку на CPU.
Общая память устраняет часть этих затрат, но создаёт новые риски: процессы могут одновременно изменить одни и те же байты, увидеть промежуточное состояние структуры или забыть освободить именованный сегмент. GIL здесь не защищает данные: у каждого процесса собственный интерпретатор и собственный GIL.
При создании SharedMemory операционная система выделяет именованный участок памяти. Другой процесс получает этот участок по имени и отображает его в своё адресное пространство. Объекты SharedMemory в процессах разные, но их buf ссылается на один общий участок памяти.
Пример показывает запись дочернего процесса и чтение родительского после join:
buf предоставляет байтовый буфер, а не обычный общий Python-объект. Для чисел и структур можно использовать struct, array или NumPy поверх поддерживаемого буфера. Ссылки на списки, словари и другие объекты Python в shared memory автоматически не сохраняются.
join в примере нужен не для предоставления взаимного доступа, а для ожидания завершения записи. Если процессы работают одновременно, применяют multiprocessing.Lock, Event, Semaphore или протокол версий данных. Блокировка защищает критическую секцию, но может уменьшить параллелизм; отсутствие блокировки повышает производительность только при корректном разделении областей записи.
Нужно явно вызвать close() в каждом процессе и один раз вызвать unlink() владельцу сегмента. Иначе могут остаться неосвобождённые системные ресурсы. Shared memory также не делает сложную составную операцию атомарной: изменение нескольких байтов или полей может наблюдаться частично.
Сервис обрабатывает изображения в нескольких процессах. Передача каждого изображения через Queue приводит к заметным затратам на сериализацию и копирование.
Вариант с Queue проще и автоматически переносит границы сообщений, но плохо масштабируется для крупных буферов. Вариант с SharedMemory быстрее для больших массивов, однако требует самостоятельно передавать имя сегмента, размер, формат данных и правила владения, а также отдельно синхронизировать доступ.
Рациональное решение — размещать пиксели в shared memory, а через очередь передавать только имя сегмента, размер и метаданные. Один процесс владеет записью конкретного буфера, обработчики читают его после сигнала готовности, затем владелец закрывает и удаляет сегмент. Такой протокол уменьшает копирование, сохраняя предсказуемость доступа.
Дополнительный вопрос 1: Гарантирует ли SharedMemory атомарность изменения нескольких байтов?
Нет. Shared memory обеспечивает общий участок памяти, но не транзакции и не блокировку. Если один процесс обновляет несколько полей, другой может прочитать комбинацию старых и новых значений. Для согласованного снимка нужны блокировка, двойная буферизация, протокол версий или другой механизм синхронизации.
Дополнительный вопрос 2: Можно ли хранить в SharedMemory обычный список Python?
Нет, напрямую нельзя. Список содержит указатели на объекты, размещённые в адресном пространстве конкретного процесса; эти адреса бессмысленны в другом процессе. В shared memory размещают байтовое представление данных, например массив чисел, а получатель интерпретирует его по заранее согласованному формату.
Дополнительный вопрос 3: Чем shared memory отличается от памяти, унаследованной после fork?
После fork дочерний процесс первоначально получает копию адресного пространства с семантикой copy-on-write. Общими физически страницы остаются только до первой записи, после чего изменённая страница отделяется. SharedMemory предназначена для явного совместного доступа и сохраняет видимость записей между процессами независимо от того, каким способом они были запущены, но требует управления именем, временем жизни и синхронизацией.