> Есть ли общая память между разными процессами (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Avito

Стек: Go

> Пример ответа

Короткий ответ

Да, между разными процессами существует общая память, но не в том виде, как между горутинами в одном процессе. В Go для межпроцессного взаимодействия используются разделяемая память через mmap, файлы, отображаемые в память, или SysV shared memory. Однако стандартный подход в Go - это IPC через каналы, сокеты или разделяемые файлы, поскольку Go не предоставляет встроенной поддержки shared memory из коробки. Разделяемая память требует синхронизации через мьютексы или семафоры, иначе возникают гонки данных.

Подробное объяснение

В операционной системе каждый процесс имеет изолированное виртуальное адресное пространство. Однако существуют механизмы, позволяющие нескольким процессам отображать один и тот же физический регион памяти в свои адресные пространства:

  • mmap с MAP_SHARED - самый распространённый способ. Файл или анонимная область памяти отображается в адресное пространство нескольких процессов. Изменения, сделанные одним процессом, видны другим.
  • SysV shared memory (shmget/shmat) - классический механизм System V, работает на уровне ядра.
  • POSIX shared memory (shm_open) - более современный вариант, работает через файловую систему /dev/shm.

В Go нет встроенной библиотеки для работы с shared memory в стандартной библиотеке. Но можно использовать:

  • syscall.Mmap для отображения файла в память.
  • Внешние библиотеки, например github.com/edsrzf/mmap-go.
  • Для SysV shared memory - cgo и системные вызовы.

Ключевая особенность: разделяемая память - это низкоуровневый механизм. Она не даёт никакой синхронизации. Если два процесса пишут в одну область без блокировок, результат непредсказуем. Поэтому обязательно нужны:

  • мьютексы (pthread mutex, futex),
  • семафоры (sem_t, SysV semaphores),
  • атомарные операции (atomic package в Go).

Альтернативный подход - не использовать общую память напрямую, а применять:

  • Unix domain sockets - быстрый IPC для локальных процессов.
  • Каналы через net.Pipe - если процессы связаны через fork/exec.
  • Файлы с блокировками (flock) - для простых сценариев.
  • gRPC или HTTP - для распределённых систем.

На практике

В реальных Go-приложениях shared memory используется редко. Причины:

  1. Go-модель concurrency построена на горутинах и каналах, а не на разделяемой памяти.
  2. Сборщик мусора Go не управляет памятью, отображённой через mmap - это создаёт сложности с жизненным циклом.
  3. Отладка гонок между процессами значительно сложнее, чем между горутинами (нет встроенного race detector для межпроцессных взаимодействий).

Типичные сценарии, где shared memory оправдана:

  • Высокопроизводительные кэши - когда нужно минимизировать задержки и избежать копирования данных между процессами.
  • Обмен большими бинарными данными - например, видеокадрами или снимками памяти.
  • Кластерные БД - некоторые СУБД (например, PostgreSQL) используют shared memory для буферного кэша.

Если вы пишете микросервисы на Go, почти наверняка вам достаточно:

  • HTTP/gRPC для межсервисного взаимодействия,
  • очереди сообщений (NATS, RabbitMQ) для асинхронных задач,
  • Redis для общего кэша.

Пример кода

Пример использования mmap для разделяемой памяти между процессами через файл:

GO
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
// Создаём файл для отображения
f, err := os.Create("/tmp/shared_mem.bin")
if err != nil {
panic(err)
}
defer f.Close()
// Устанавливаем размер файла
if err := f.Truncate(4096); err != nil {
panic(err)
}
// Отображаем файл в память
data, err := syscall.Mmap(
int(f.Fd()), 0, 4096,
syscall.PROT_READ|syscall.PROT_WRITE,
syscall.MAP_SHARED,
)
if err != nil {
panic(err)
}
defer syscall.Munmap(data)
// Пишем данные
copy(data, []byte("hello from process"))
// Читаем данные
fmt.Println(string(data[:5]))
}

Второй процесс может открыть тот же файл и прочитать данные. Для синхронизации нужно использовать отдельный механизм, например семафор:

GO
// Пример с семафором через syscall (упрощённо)
sem, _ := syscall.Semget(1234, 1, syscall.IPC_CREAT|0666)
syscall.Semop(sem, &syscall.Sembuf{Op: -1}, 1) // lock
// ... работа с shared memory ...
syscall.Semop(sem, &syscall.Sembuf{Op: 1}, 1) // unlock

Как отвечать на собеседовании

Начните с чёткого ответа: "Да, существует, но в Go это не основной механизм". Затем:

  1. Кратко перечислите способы: mmap, SysV shm, POSIX shm.
  2. Объясните, почему в Go это редко используется: модель concurrency, GC, сложность синхронизации.
  3. Приведите альтернативы: сокеты, каналы, очереди.
  4. Упомяните, когда shared memory действительно нужна: высокие нагрузки, большие данные.
  5. Если спросят про синхронизацию - расскажите про мьютексы, семафоры, атомарные операции.

Не углубляйтесь в детали реализации, если не просят. Покажите понимание trade-off: скорость vs сложность.

Что проверяет интервьюер

  • Понимание различий между процессами и потоками/горутинами.
  • Знание механизмов IPC в Unix-системах.
  • Умение оценивать, когда использовать низкоуровневые механизмы, а когда - высокоуровневые.
  • Понимание проблем синхронизации и гонок данных.
  • Практический опыт работы с системными вызовами в Go.

Типичные ошибки

  • Утверждение, что между процессами нет общей памяти вообще - это неверно.
  • Путаница между shared memory и memory-mapped files - это одно и то же в контексте mmap.
  • Игнорирование необходимости синхронизации - главная ошибка.
  • Предложение использовать shared memory для микросервисов без веских причин.
  • Незнание, что Go race detector не работает между процессами.
  • Попытка использовать каналы Go между разными процессами - это невозможно, каналы работают только внутри одного процесса.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью