Вопросы по горутинам и каналам 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 кладут первым аргументом, что произойдёт с горутиной, которая его не проверяет, и чем отличаются варианты с таймаутом и отменой.