EnglishРусский Map

Передача сообщений против общей памяти

title
Передача сообщений против общей памяти
type
concept
summary
Два лагеря координации параллелизма, тезис об общей причине их сбоев и то, что каждый из них даёт на самом деле
tags
concurrency, distributed-systems
created
2026-04-25
updated
2026-09-14
lang
ru
source_updated
2026-09-14
translated
2026-09-14
translator
lllm/antigravity/gemini-3.7-flash-medium

Последние несколько десятилетий доминирующий взгляд на параллелизм делит всё на два лагеря: координацию через общую память (блокировки, мьютексы, семафоры, атомики) и координацию через передачу сообщений (каналы, почтовые ящики, акторы). Главный аргумент в пользу передачи сообщений состоит в том, что она избавляет от граблей общего состояния, изолируя воркеров и позволяя им только обмениваться значениями.

Эдвард Ли (Edward Lee) оспорил такое деление в 2006 году в статье The Problem with Threads. Его аргумент: оба подхода моделируют параллелизм как потоки исполнения, требующие координации. Поэтому смена механизма координации с блокировок на сообщения не меняет базовую модель - она лишь меняет синтаксис сбоев. Оба подхода наследуют недетерминизм. В обоих программист вынужден отсекать недетерминизм вместо того, чтобы выражать вычисления.

Что передача сообщений устраняет на самом деле

Передача сообщений действительно исключает один важный класс ошибок: несинхронизированный доступ к памяти. Если два воркера общаются исключительно отправкой значений, они не могут одновременно модифицировать одну и ту же переменную. Состояния гонки по данным (data race) в строгом смысле исчезают.

Чего она не устраняет:

  • Взаимные блокировки (deadlocks) - циклическое ожидание, выраженное через очереди вместо блокировок.
  • Утечки - заблокированный отправитель или получатель, привязанный к очереди, которую никто больше не читает.
  • Недетерминированное планирование - когда несколько воркеров могут читать из одной очереди, среда выполнения выбирает одного из них произвольно.
  • Нарушения протокола - неверный порядок сообщений, отправка после закрытия, повторное закрытие.

Все эти категории напрямую соответствуют классическим ошибкам разделяемого состояния. Четырёхстороннее сопоставление с примерами из реальных проектов на Go описано в go-channel-bug-patterns.

Почему дихотомия ложна

Аргумент упирается в то, чем на самом деле является примитив передачи сообщений. Настоящий канал - вроде тех, что описаны в пи-исчислении (pi-calculus) или реализованы в полноценных библиотеках функциональных каналов, - имеет две чётко разделённые конечные точки (producer и consumer) с разными типами и возможностями. Среда выполнения может обнаружить исчезновение одного конца и освободить другой.

У большинства промышленных примитивов передачи сообщений этого нет. Канал в Go - это единый объект конкурентной очереди: любая горутина, держащая на него ссылку, может как отправлять в него, так и читать из него. BlockingQueue в Java структурно идентична и живёт в java.util.concurrent рядом с Mutex и Semaphore. И то, и другое - разделяемые мутабельные структуры данных, используемые для координации. Словарь различается, но суть одна.

Даже Erlang, канонический язык с передачей сообщений, изолированными кучами и копированием данных, поставляется с таблицами ETS: разделяемым мутабельным хранилищем, добавленным из-за того, что чистая модель акторов не укладывалась в требования по производительности. Исследователи находили гонки в ETS даже в самой стандартной библиотеке Erlang.

Что из этого следует

Эмпирическим подтверждением служит исследование ошибок Ту и соавторов (Tu et al., 2019): 58% блокирующих багов в Docker, Kubernetes, etcd, gRPC и CockroachDB были вызваны передачей сообщений, а не общей памятью. Подробный разбор приведён в message-passing-shared-mutable-state.

Если оба механизма координации ломаются по одной и той же структурной причине - сам объект координации представляет собой разделяемое мутабельное состояние, - выбор между ними сводится к стилю, а не к безопасности. Более глубокий вопрос заключается в том, годится ли хоть один из этих подходов в качестве правильного фундамента вообще, - именно эту проблему поднимает автор causality-blog.

Связанные страницы

  • go-channel-bug-patterns - четыре классических сценария сбоев, воспроизводимых каналами
  • data-race - единственный класс ошибок, который действительно устраняет передача сообщений, и почему он уже, чем состояние гонки в общем смысле
  • shared-memory-consistency-causality - какие гарантии на самом деле даёт аппаратная модель памяти, лежащая в основе shared memory, и где атомики C++ ошибаются с причинностью
  • art-of-multiprocessor-programming - учебник Херлихи и Шавита (Herlihy and Shavit), куда стоит заглянуть по теме разделяемой памяти: linearizability, lock-free структуры и иерархия консенсуса, ранжирующая примитивы синхронизации по их выразительной силе
  • distributed-consensus - взгляд на координацию множества воркеров через призму теорем о невозможности
  • flp-impossibility - почему ни один алгоритм не гарантирует консенсус в асинхронных системах со сбоями узлов
Sub-pages