Делает ли multiprocessing.shared_memory.SharedMemory запись в общий буфер атомарной между процессами?
Нет. SharedMemory предоставляет общий участок памяти, но не гарантирует атомарность операций и не защищает от гонок данных. Для согласованного чтения и изменения общего состояния нужна отдельная синхронизация, например multiprocessing.Lock.
Процессы Python имеют изолированные адресные пространства, поэтому обычные объекты между ними напрямую не разделяются. Механизм общей памяти появился как способ обмениваться большими массивами и буферами без постоянного копирования и сериализации через очереди или каналы.
Это решает проблему дорогого обмена данными, но намеренно не объединяет обмен данными с синхронизацией. Управление моментом доступа остаётся ответственностью приложения.
Если два процесса одновременно выполняют последовательность «прочитать значение — изменить — записать», они могут прочитать одно и то же исходное значение. Последняя запись затрёт результат другого процесса, и итог окажется меньше ожидаемого.
Видимость общей памяти также не означает согласованность составной операции. Даже если отдельная запись байта на конкретной платформе физически выполняется неделимо, Python API SharedMemory не предоставляет общего контракта атомарности для произвольных операций над буфером.
Все участники, изменяющие связанные данные, должны использовать один и тот же объект синхронизации. Блокировка должна охватывать всю критическую секцию: чтение текущего значения, вычисление нового значения и запись результата.
Здесь память общая, но изменение счётчика последовательно защищено блокировкой. GIL эту проблему не решает: процессы имеют разные интерпретаторы и разные GIL, а сама общая память не превращает несколько операций Python в одну атомарную операцию.
Блокировка создаёт компромисс: она обеспечивает корректность, но уменьшает параллелизм и может стать узким местом. Для высококонкурентных сценариев иногда лучше использовать разделение данных, локальные накопители с последующим объединением или специализированные атомарные примитивы, если они доступны для выбранного формата данных.
Необходимо также корректно закрывать объект SharedMemory в каждом процессе и освобождать системный ресурс через unlink после завершения работы. Синхронизация должна защищать не только записи, но и чтения, если читателю нужна согласованная версия нескольких связанных полей.
Сервис обрабатывает изображения в нескольких процессах и хранит общий буфер результатов в SharedMemory. Каждый процесс записывает результат и увеличивает общий счётчик обработанных изображений.
Вариант с очередью проще для программирования и автоматически передаёт сообщения между процессами, но копирует или сериализует большие данные. Вариант с общей памятью быстрее для крупных буферов, однако требует отдельно проектировать блокировки, формат данных, жизненный цикл сегмента и обработку аварийного завершения.
Выбранный вариант — SharedMemory для самих изображений и отдельная блокировка только для метаданных и счётчика. Это сохраняет преимущество обмена большими данными без копирования, а узкая критическая секция ограничивает потери параллелизма. При завершении процессов сегмент закрывается, а владелец удаляет его, чтобы не оставить системный ресурс.
Нет, если операция чтения и записи образует единое изменение состояния. Чтение вне блокировки может получить промежуточное или уже устаревшее состояние, а вычисление нового значения на основе такого чтения приведёт к потере обновлений. Блокировка должна охватывать весь протокол доступа, и его обязаны соблюдать все процессы.
Нет. Блокировка не меняет свойства буфера и не делает его операции атомарными на уровне памяти. Она лишь организует соглашение: пока один участник находится в критической секции, остальные участники этого соглашения туда не входят. Процесс, который обращается к буферу в обход блокировки, по-прежнему может нарушить корректность.
Нет, обычный threading.Lock предназначен для потоков внутри одного процесса и не является межпроцессным примитивом. Для процессов нужен объект вроде multiprocessing.Lock либо другой механизм межпроцессной синхронизации. При этом сам способ запуска процессов, например spawn или fork, влияет на то, как этот объект будет передан дочернему процессу, но не отменяет требования использовать общий межпроцессный примитив.