Горутины и синхронизация

Основные инструменты конкурентности в Go — просто и по делу

🧵 Что такое горутина?

Горутина — это легковесный поток, которым управляет Go Runtime, а не операционная система. Создать горутину в тысячи раз дешевле, чем обычный поток ОС.

Вместо дорогих строительных бригад (потоков ОС) вы нанимаете армию быстрых дешёвых помощников. Каждый получает маленькую задачу: «принести кирпич», «замесить цемент». Супер-менеджер (Go Runtime) умно распределяет их между ядрами процессора.
Один поток

Вы один роете котлован, мешаете бетон, кладёте кирпичи. Пока ждёте доставку — просто стоите. Дом строится очень долго.

Потоки ОС

Нанять несколько бригад — эффективно, но дорого. Каждая требует своей техники и документов. 1000 бригад — разорение.

Ключевые характеристики

Когда завершается главная горутина main — вся программа останавливается, даже если другие горутины ещё не закончили работу.

⏳ sync.WaitGroup — ждём завершения горутин

sync.WaitGroup решает главную проблему: как сообщить программе, что нужно подождать завершения всех запущенных горутин.

Это ваш магический чек-лист. Вы не уходите домой, пока в нём есть невычеркнутые пункты.
1
wg.Add(N) — добавить задачи
Перед запуском горутин говорим: «Сегодня у нас N задач». Делаем пометку в списке.
2
wg.Done() — задача выполнена
Горутина завершила работу и сообщает об этом. Один пункт вычёркивается из списка. Используйте defer wg.Done() — это гарантирует вызов даже при ошибке.
3
wg.Wait() — ждать пустого списка
Программа «зависает» на этой строке, пока счётчик не обнулится. Только тогда выполнение продолжается.
Важно: всегда вызывайте wg.Add() до запуска горутины через go. Иначе Wait() может сработать раньше, чем горутина успеет зарегистрироваться.

🏁 Состояние гонки (Data Race)

Гонка данных возникает, когда две или более горутины обращаются к одному участку памяти одновременно, и хотя бы одна из них записывает данные. Результат становится непредсказуемым.

Двое редактируют один документ без синхронизации. Первый исправляет «мир» → «Go». Второй, не видя правки, исправляет «Привет» → «Hello». Итог: «Hello, мир» вместо «Hello, Go». Правка первого потеряна.

Три условия для гонки данных


🔒 Мьютексы: sync.Mutex и sync.RWMutex

Мьютексы защищают общее состояние — сложные структуры данных, кэши, конфигурации — от одновременного доступа нескольких горутин.

sync.Mutex
«Тупой» замок. Только одна горутина держит ключ — неважно, читает или пишет. Все остальные стоят в очереди.
sync.RWMutex
Умный замок для сценария «много читателей, мало писателей». Читать могут все сразу, но запись — только один и блокирует всех.
Mutex: только один человек в читальном зале — хоть читает, хоть пишет.
RWMutex: многие могут читать одновременно. Но если кто-то хочет написать — просит всех выйти и запирает дверь.

Когда использовать мьютекс


⚡ Атомарные операции: sync/atomic

«Атомарный» означает «неделимый». Атомарная операция гарантированно выполняется как единое целое — другие горутины не могут увидеть её «наполовину выполненной».

Обычный counter++ — это три шага: прочитать → увеличить → записать. Гонка данных происходит в «щели» между этими шагами. Атомарная операция выполняет все три шага за один приём на уровне процессора.

Неатомарно: просовываете в окно документ, потом ручку, потом конверт — по одному. Кто-то может захлопнуть окно между действиями.
Атомарно: передаёте всё одним пакетом. Никто не может вмешаться.

Плюсы и минусы atomic

ПлюсыМинусы
Максимальная производительность для простых операций Сложнее читать и отлаживать
Не блокирует горутины, нет переключения контекста Работает только с примитивными типами (int32, int64, указатели)
Прямой доступ к инструкциям процессора Неправильное использование приводит к редким и сложным багам

🗺 Когда что выбирать

СитуацияИнструментПочему
Сложная структура данных (мапа, слайс) sync.Mutex atomic не умеет работать со сложными типами
Много читателей, мало писателей sync.RWMutex Читатели не блокируют друг друга
Простой счётчик, производительность критична sync/atomic Нет накладных расходов на блокировку
Вы не уверены, что выбрать sync.Mutex Безопаснее, проще, правильно в 99% случаев
Золотое правило: всегда начинайте с sync.Mutex. Переходите на sync/atomic только при доказанном узком месте в производительности — например, после профилирования через pprof.