Дедлок и нереентерабельность
Ночью конвейер встал: файлы перестали обрабатываться, алерты молчали до утра. Разбор показал дедлок — один путь дважды брал блокировку и вешал сам себя. Миша разделил код, и утренний деплой вернул конвейер в строй.
мьютексы дедлочатся двумя способами: циклическим порядком захвата и повторным захватом той же горутиной — Go-мьютекс нереентерабелен.
Циклический порядок
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Классический дедлок: две горутины захватывают одни и те же мьютексы в разном порядке. Каждая ждёт ту блокировку, которую держит другая. Лекарство — единый порядок захвата во всём коде: всегда mu1 раньше mu2, тогда цикл невозможен.
Нереентерабельность
func (s *Stats) Snapshot() int {
s.mu.Lock()
defer s.mu.Unlock()
return s.Value() // Value сам делает Lock — deadlock
}
func (s *Stats) Value() int {
s.mu.Lock()
defer s.mu.Unlock()
return s.processed
}
В отличие от блокировок в Java или C#, Go-мьютекс нереентерабелен: повторный Lock той же горутиной — дедлок, а не «счётчик вложенности». Вызвал метод, который сам берёт мьютекс, из секции того же мьютекса — повесился. Лечится разделением: публичный метод берёт мьютекс, внутренний — нет (valueLocked).
Почему нереентерабельность — это хорошо
Звучит как ограничение, но это осознанный выбор: реентерабельность поощряет «просто заверни всё в мьютекс» — а это прямая дорога к длинным секциям и скрытым гонкам по инвариантам. Нереентерабельность заставляет думать, где граница секции и кто владеет данными, — ровно то, чему посвящён следующий урок.
Вы узнаете:
- как рантайм детектит дедлок
- как RWMutex связан с дедлоком рекурсивного RLock
Проверьте себя
var mu sync.Mutex; mu.Lock(); mu.Lock() в одной горутине. Что произойдёт?
Как лечить циклический захват двух мьютексов в разном порядке?
Почему нереентерабельность — фича, а не баг?
правильных ответов
Дедлоки: разный порядок захвата и повторный Lock. Единый порядок захвата + разделение публичных и внутренних методов.
10 000 ₽ · 60 минут · онлайн