Модели конкурентности: потоки, события, акторы, CSP
Потоки и async — не единственные способы думать о конкурентности. Есть и другие модели.
Модель конкурентности — это способ организовать взаимодействие параллельных задач: через общую память и блокировки или через передачу сообщений.
Все подходы делятся на два больших лагеря. Первый — разделяемая память: задачи трогают общие данные, синхронизируясь блокировками. Второй — передача сообщений: задачи ничего не делят, а обмениваются сообщениями. Второй подход обычно безопаснее: нет общего изменяемого состояния — нет гонок.
Четыре распространённые модели
| Модель | Как общаются | Пример |
| Потоки + общая память | через общие переменные под блокировками | Python threading, Java |
| Событийная (event loop) | колбэки/корутины в одном потоке | asyncio, Node.js |
| Акторы | сообщения в «почтовый ящик» актора | Erlang, Akka |
| CSP / каналы | передача по каналам между процессами | Go (goroutines + channels) |
Акторы
В модели акторов каждый актор — изолированная сущность со своим состоянием и почтовым ящиком. Снаружи к состоянию не подобраться; можно лишь прислать сообщение. Актор обрабатывает сообщения по одному, поэтому внутри него гонок нет «по построению». Так устроены телеком-системы на Erlang.
CSP и каналы
CSP (Communicating Sequential Processes) — основа конкурентности в Go. Задачи (горутины) общаются через каналы: один пишет в канал, другой читает. Девиз — «не общайтесь через общую память; делите память, общаясь». Канал сам синхронизирует передачу.
import collections
# смоделируем канал как очередь: producer кладёт, consumer берёт
channel = collections.deque()
channel.append("msg-1") # producer -> канал
channel.append("msg-2")
while channel:
msg = channel.popleft() # канал -> consumer
print("получено:", msg)Вывод:
получено: msg-1 получено: msg-2
Как работает под капотом
Модели с передачей сообщений прячут синхронизацию внутри почтовых ящиков и каналов: разработчик не пишет блокировки руками. Это снижает класс ошибок (гонки, забытые release), но добавляет другие заботы — переполнение очередей, дедлоки на каналах (читатель ждёт сообщение, которое никто не пришлёт), сериализацию сообщений. В Python чистой модели акторов в стандартной библиотеке нет, но идею каналов хорошо передаёт queue.Queue и связка процессов с очередями в multiprocessing.
Частые ошибки
- Считать одну модель универсальной. Для CPU лучше процессы, для тысяч соединений — события, для устойчивых распределённых систем — акторы.
- Смешивать общую память и сообщения бессистемно. Тогда теряются преимущества обеих моделей.
- Игнорировать переполнение каналов/ящиков. Без ограничения размера producer может «завалить» consumer.
Итог
- Два лагеря: общая память с блокировками и передача сообщений.
- Передача сообщений безопаснее — нет общего изменяемого состояния.
- Акторы изолируют состояние, CSP/каналы синхронизируют передачу.
- Выбор модели зависит от задачи; в Python каналы моделируются очередями.