Передача сообщений против общей памяти
- title
- Передача сообщений против общей памяти
- type
- concept
- summary
- Два лагеря координации параллелизма, тезис об общей причине их сбоев и то, что каждый из них даёт на самом деле
- tags
- concurrency, distributed-systems
- created
- 2026-04-25
- updated
- 2026-09-14
- lang
- ru
- translation_of
- message-passing-vs-shared-memory
- 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 - почему ни один алгоритм не гарантирует консенсус в асинхронных системах со сбоями узлов