Конкурентный доступ к map
Прод упал с фатальной ошибкой времени выполнения — два шага конвейера одновременно писали в общий индекс файлов. Перезапуск падение не повторил: такая гонка ловилась не всегда, и ошибка могла не появиться вовсе. Индекс закрыли замком, и падения прекратились.
карта безопасна для чистого конкурентного чтения, но не для записи: запись или пара «чтение плюс запись» из нескольких горутин — гонка данных, которую рантайм может прервать фатальной ошибкой, но не обязан обнаружить; запись защищают мьютексом, а sync.Map — только для особых случаев.
Concurrent map writes
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Это не паника, а фатальная ошибка времени выполнения: она не перехватывается recover и завершает процесс. Но диагностика не гарантирована: детект рантайма вероятностный — ошибка может выскочить при первом же запуске, а может не выскочить никогда, и программа молча испортит данные. Поэтому в тестах гонку не поймать без race detector'а, да и -race ловит только пройденные пути. Гонка «чтение плюс запись» небезопасна точно так же, хотя в сообщении рантайма фигурирует только запись.
Защита мьютексом
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Мьютексы — отдельный урок, здесь зафиксируй правило: карта не потокобезопасна для записи — любая запись, а также смешанный доступ «чтение плюс запись» из нескольких горутин требуют внешней синхронизации. Чистое конкурентное чтение без записей безопасно и блокировок не требует (go.dev/doc/faq#atomic_maps).
Sync.Map — для особых случаев
sync.Map — потокобезопасная карта, но она не «просто карта с мьютексом». Она выигрывает в двух сценариях: ключ пишется один раз, а читается много (кэш конфигурации), и когда разные горутины работают с разными ключами. В обычных случаях документация рекомендует обычную map с блокировкой: она проще, а выигрыш sync.Map узкий — его подтверждают бенчмарком. Подробно — в уроке.
Вы узнаете:
- почему
concurrent map writes— fatal, а не panic - как race detector ловит гонку в тестах
Проверьте себя
Две горутины одновременно пишут в одну map. Что может произойти?
Когда sync.Map действительно выигрывает?
Чистое конкурентное чтение map без записей — безопасно?
правильных ответов
Карта безопасна для чистого конкурентного чтения; запись и смешанный доступ «чтение плюс запись» — гонка, которую рантайм может прервать fatal error, но не обязан поймать. Защита записи — map + RWMutex; sync.Map — только для сценариев «пишем редко, читаем часто».
10 000 ₽ · 60 минут · онлайн