Что такое разделяемая память (shared memory) и как ее использовать в Python

Ответ

Разделяемая память (shared memory) — это механизм межпроцессного взаимодействия (IPC), при котором несколько независимых процессов получают доступ к одному и тому же участку физической памяти. Это один из самых быстрых способов обмена данными, так как исключает затраты на копирование данных между процессами.

В Python, начиная с версии 3.8, для работы с разделяемой памятью используется модуль multiprocessing.shared_memory.

Ключевые шаги:

  1. Создание: Один процесс создает блок разделяемой памяти с помощью shared_memory.SharedMemory(create=True, size=...).
  2. Подключение: Другие процессы подключаются к этому блоку по его уникальному имени shared_memory.SharedMemory(name=...).
  3. Работа с данными: Данные записываются и читаются через буфер (.buf), который представляет собой memoryview.
  4. Освобождение ресурсов: Каждый процесс должен закрыть свой экземпляр (.close()), а создавший процесс — дополнительно удалить блок из системы (.unlink()).

Пример: один процесс пишет, другой читает

import multiprocessing as mp
from multiprocessing import shared_memory
import time

def writer(shm_name):
    # Подключаемся к существующей памяти
    existing_shm = shared_memory.SharedMemory(name=shm_name)
    print("Writer: подключился к памяти.")
    # Записываем данные в первые 5 байт
    message = b'Hello'
    existing_shm.buf[:len(message)] = message
    print("Writer: записал данные.")
    # Закрываем доступ
    existing_shm.close()

def reader(shm_name):
    time.sleep(1) # Ждем, пока writer запишет данные
    # Подключаемся к существующей памяти
    existing_shm = shared_memory.SharedMemory(name=shm_name)
    print("Reader: подключился к памяти.")
    # Читаем первые 5 байт
    read_message = bytes(existing_shm.buf[:5])
    print(f"Reader: прочитал данные - {read_message.decode()}")
    existing_shm.close()

if __name__ == "__main__":
    # 1. Создаем блок разделяемой памяти
    shm = shared_memory.SharedMemory(create=True, size=10)
    print(f"Main: создал блок памяти с именем '{shm.name}'")

    # 2. Запускаем процессы
    p_writer = mp.Process(target=writer, args=(shm.name,))
    p_reader = mp.Process(target=reader, args=(shm.name,))

    p_writer.start()
    p_reader.start()

    p_writer.join()
    p_reader.join()

    # 3. Освобождаем и удаляем ресурсы
    shm.close()
    shm.unlink()
    print("Main: память освобождена.")

Важные аспекты:

  • Синхронизация: Разделяемая память не обеспечивает синхронизацию. Для предотвращения гонки данных (race conditions) необходимо использовать примитивы синхронизации, такие как multiprocessing.Lock или Semaphore.
  • Управление жизненным циклом: Крайне важно корректно вызывать close() и unlink(), чтобы избежать утечек ресурсов в операционной системе.

Ответ 18+ 🔞

А, слушай, вот тебе про разделяемую память, это же просто пиздец какой удобный механизм, если процессы между собой общаться хотят быстро, без всей этой ебалы с очередями и сокетами. Представь, два процесса смотрят на один и тот же кусок оперативки, как два мужика на одну бутылку. Данные туда-сюда — и копировать ничего не надо, скорость — овердохуища.

В Питоне, с версии 3.8, для этого есть модуль multiprocessing.shared_memory. Главное — не накосячить с порядком действий, а то будет как в том анекдоте про Герасима, только вместо Муму — утечка памяти на весь комп.

Как это, блядь, работает:

  1. Создаёшь: Один процесс, обычно главный, кричит системе: «Дай мне вот столько байт!» через shared_memory.SharedMemory(create=True, size=...). Система выделяет и выдаёт уникальное имя, типа psm_5f4b1.
  2. Цепляешься: Другие процессы, как пидары на халяву, подключаются к этому же блоку по этому самому имени: shared_memory.SharedMemory(name=...).
  3. Работаешь: Все лезут в один буфер (.buf), который ведёт себя как memoryview. Пишешь туда байты, читаешь оттуда — красота.
  4. Убираешь за собой: Это самое главное, ёпта! Каждый процесс должен вызвать .close(), а тот, кто создал — ещё и .unlink(), чтобы система этот кусок памяти окончательно освободила. Иначе он так и будет висеть, как призрак, пока систему не перезагрузишь.

Вот, смотри, пример: один пишет, другой читает

import multiprocessing as mp
from multiprocessing import shared_memory
import time

def writer(shm_name):
    # Цепляемся к уже созданной памяти
    existing_shm = shared_memory.SharedMemory(name=shm_name)
    print("Писака: подключился к памяти.")
    # Пишем 'Hello' в первые 5 байт
    message = b'Hello'
    existing_shm.buf[:len(message)] = message
    print("Писака: записал данные.")
    # Закрываем свой доступ
    existing_shm.close()

def reader(shm_name):
    time.sleep(1) # Ждём чутка, пока писака своё дело сделает
    # Тоже цепляемся
    existing_shm = shared_memory.SharedMemory(name=shm_name)
    print("Читака: подключился к памяти.")
    # Читаем первые 5 байт
    read_message = bytes(existing_shm.buf[:5])
    print(f"Читака: прочитал данные - {read_message.decode()}")
    existing_shm.close()

if __name__ == "__main__":
    # 1. Главный создаёт блок памяти, скажем, на 10 байт
    shm = shared_memory.SharedMemory(create=True, size=10)
    print(f"Главный: создал блок памяти с именем '{shm.name}'")

    # 2. Запускаем оба процесса
    p_writer = mp.Process(target=writer, args=(shm.name,))
    p_reader = mp.Process(target=reader, args=(shm.name,))

    p_writer.start()
    p_reader.start()

    p_writer.join()
    p_reader.join()

    # 3. УБИРАЕМ ЗА СОБОЙ, БЛЯДЬ!
    shm.close()
    shm.unlink()
    print("Главный: память освобождена.")

А теперь, внимание, важные моменты, где все обычно ебутся:

  • Синхронизация — твоя забота. Сама по себе разделяемая память синхронизацию не обеспечивает. Если два процесса начнут писать в одно место одновременно, будет race condition, пиздец и каша. Нужно использовать Lock или Semaphore из того же multiprocessing, чтобы доступ был по очереди, как в нормальной бане.
  • Жизненный цикл. Если забыть unlink(), блок памяти останется в системе. Это как оставить после вечеринки пустые бутылки — операционка тебя не похвалит. Утечки, блядь.