Вопросы по горутинам и каналам Go на собеседовании
Конкурентность это то, ради чего берут Go, поэтому спрашивают её всегда. Разговор быстро уходит от определения горутины к практике: что произойдёт при записи в закрытый канал и как ты отменяешь зависшую операцию.
Из чего состоит тема
Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, что просели.
- Каналы11
- Горутины10
- context: отмена9
- WaitGroup и Once8
- Mutex и atomic5
- select и паттерны5
- Гонки данных5
- Ловушки каналов5
- Паттерны синхронизации4
- Утечки горутин4
Разборы подтем
Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.
- Горутины в Go10 вопросов
- Каналы в Go11 вопросов
- select в Go5 вопросов
- Ловушки каналов в Go5 вопросов
- Утечки горутин в Go4 вопросов
- Mutex и atomic в Go5 вопросов
- WaitGroup и Once в Go8 вопросов
- context в Go9 вопросов
- Гонки данных в Go5 вопросов
- Паттерны синхронизации в Go4 вопросов
Примеры вопросов с разбором
- Горутина выполняет ch <- v, но канал к этому моменту уже закрыт. Что будет?A)Значение молча отбрасываетсяB)v кладётся в буфер и прочитается после переоткрытия каналаC)Паника рантайма: send on closed channelD)Отправка блокируется до повторного открытия канала
показать ответ и разбор
+C)Паника рантайма: send on closed channel// разбор: Отправка в закрытый канал — паника рантайма «send on closed channel». Каналы не переоткрываются: close необратим. Отсюда классическая гонка — писатель шлёт после того, как канал закрыл кто-то другой. Лечится координацией: закрывает один владелец, а писатели узнают об остановке через отдельный сигнал (context/done) и прекращают отправку до close.
- Небуферизованный канал. Что делает отправка ch <- v, если получателя ещё нет?A)Блокирует горутину-отправителя, пока другая горутина не примет значениеB)Кладёт значение во внутренний буфер канала и немедленно продолжает работу дальшеC)Возвращает ошибку об отсутствии получателяD)Молча отбрасывает значение, раз его некому принять
показать ответ и разбор
+A)Блокирует горутину-отправителя, пока другая горутина не примет значение// разбор: У небуферизованного канала (cap 0) отправка и приём — рандеву: отправитель блокируется, пока другая горутина не будет готова принять (и симметрично наоборот). Буфера нет, поэтому «положить и уйти» невозможно. Если принять окажется некому и других горутин нет — рантайм фиксирует дедлок с фатальной ошибкой.
- Что называют утечкой горутины (goroutine leak)?A)Горутину, которая вечно заблокирована и потому не завершаетсяB)Горутину, съедающую слишком много CPU в бесконечном циклеC)Утечку памяти из-за того, что горутина не вызвала runtime.GC()D)Ситуацию, когда горутин создано больше, чем значение GOMAXPROCS
показать ответ и разбор
+A)Горутину, которая вечно заблокирована и потому не завершается// разбор: Утечка — горутина, которая никогда не завершится, потому что вечно ждёт события, которое не наступит: приём из канала без отправителя, отправку в канал без читателя, ожидание на sync-примитиве. Она держит свой стек и захваченные переменные, и память не освобождается. GC заблокированную горутину не собирает — формально она «жива». Копятся тихо, всплывают под нагрузкой.
- Что такое горутина в Go?A)Системный поток ОС, создаваемый напрямую через syscall на каждый вызов goB)Процесс операционной системы с собственным адресным пространствомC)Синтаксический сахар: код откладывается и выполняется в конце функцииD)Лёгкий поток, которым управляет рантайм Go, а не ОС
показать ответ и разбор
+D)Лёгкий поток, которым управляет рантайм Go, а не ОС// разбор: Горутина — функция, запущенная через go. Рантайм мультиплексирует тысячи горутин на небольшое число потоков ОС (модель M:N), стек стартует с ~2 КБ и растёт по мере надобности. Поэтому горутины дёшевы — их держат сотнями тысяч. Поток ОС, наоборот, тяжёлый: мегабайты стека, дорогой контекст-свитч через ядро.
- Зачем в select нужна ветка default?A)Задаёт case, который срабатывает после того, как отработают остальныеB)Ловит панику из других case, как это делает recoverC)Задаёт порядок проверки: сначала default, потом готовые каналыD)Делает операцию неблокирующей: нет готовых case — сразу default
показать ответ и разбор
+D)Делает операцию неблокирующей: нет готовых case — сразу default// разбор: Без default select блокируется, пока хотя бы один case не станет готов. Ветка default выполняется немедленно, если в момент проверки не готов ни один канал, — так делают неблокирующие приём и отправку, опросы. Если готовых case несколько, select выбирает один псевдослучайно; default вступает в игру только когда готовых нет вовсе.
- Зачем в Go нужен context.Context?A)Пронести отмену, дедлайн и request-scoped данные по цепочке вызововB)Хранить глобальное состояние приложения вместо переменных пакетаC)Заменить каналы для передачи больших объёмов данных между горутинами напрямуюD)Автоматически ограничивать память, которую может занять одна горутина
показать ответ и разбор
+A)Пронести отмену, дедлайн и request-scoped данные по цепочке вызовов// разбор: context несёт сигнал отмены и дедлайн вниз по дереву вызовов и горутин: отменили корневой — узнали все, кто слушает ctx.Done(). Плюс request-scoped значения (trace-id, дедлайн запроса). Первым аргументом ctx принимают HTTP-хендлеры, RPC, запросы к БД. Это не хранилище состояния и не транспорт данных — про управление жизненным циклом.
- Что такое гонка данных (data race) в Go?A)Ситуация, когда две горутины соревнуются, кто первой захватит мьютексB)Незащищённый конкурентный доступ к одной памяти, где хотя бы один — записьC)Недетерминированный порядок запуска горутин при каждом старте программыD)Программа ведёт себя по-разному в зависимости от того, какая горутина закончит первой
показать ответ и разбор
+B)Незащищённый конкурентный доступ к одной памяти, где хотя бы один — запись// разбор: Гонка данных — конкурентный доступ двух и более горутин к одной ячейке памяти без синхронизации, где хотя бы одна операция — запись. Результат не определён: от «повезло» до порчи данных и падений. Это уже, чем race condition (та про порядок событий вообще). Лечится синхронизацией — мьютекс, atomic, канал. Детектор -race ловит их на рантайме.
- Зачем нужен sync.Mutex?A)Ускоряет доступ к общей памяти, кэшируя её значение между горутинамиB)Взаимное исключение: секцию Lock–Unlock держит одна горутина за разC)Помечает переменную потокобезопасной, пока с ней работает горутинаD)Создаёт отдельную копию данных каждой горутине, чтобы они не пересекались
показать ответ и разбор
+B)Взаимное исключение: секцию Lock–Unlock держит одна горутина за раз// разбор: Mutex сериализует доступ к общим данным: Lock() входит в критическую секцию, Unlock() выпускает; пока один держит замок, другие горутины на Lock блокируются. Так защищают инвариант, который ломает конкурентная запись (например counter++). Это не кэш и не «магическая» безопасность переменной — защищает именно дисциплина: все идут через один и тот же мьютекс.
- Структура со встроенным sync.Mutex передана в метод с получателем-значением func (s S). В чём риск?A)Копируется и мьютекс — Lock идёт по разным копиям, защита не работаетB)Встроенный мьютекс требует, чтобы структуру передавали через указательC)Компилятор отклонит код: копировать sync.Mutex запрещеноD)Ничего: Go передаёт встроенный мьютекс по ссылке, даже если получатель — значение
показать ответ и разбор
+A)Копируется и мьютекс — Lock идёт по разным копиям, защита не работает// разбор: Получатель-значение копирует всю структуру, включая Mutex. Копия мьютекса — независимый замок с обнулённым состоянием, поэтому горутины блокируются на разных копиях и синхронизации нет (а копирование залоченного и вовсе даёт битое состояние). go vet это ловит: «passes lock by value». Правило: методы со встроенным мьютексом — на получателе-указателе. Спот-чек подтвердил варнинг.
это 9 из 66
Ещё 57 вопросов по теме — в тренажёре, с движком повторения
Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы приходят сессиями, а подтему, на которой ты споткнулся, движок принесёт снова: завтра, через три дня, через неделю. Бесплатно, с дневным лимитом вопросов.
Частые вопросы
Чем горутина отличается от потока ОС?
Горутина — это легковесная задача рантайма Go со стеком в несколько килобайт, которую планировщик раскладывает по потокам ОС. Поэтому их можно держать десятками тысяч, а потоков столько не создать.
Что спрашивают про context?
Как передают отмену и дедлайн по цепочке вызовов, почему context кладут первым аргументом, что произойдёт с горутиной, которая его не проверяет, и чем отличаются варианты с таймаутом и отменой.