ar
Feedback
Библиотека Go для собеса | вопросы с собеседований

Библиотека Go для собеса | вопросы с собеседований

الذهاب إلى القناة على Telegram

Вопросы с собеседований по Go и ответы на них. Покажем, как запустить своего ии-агента: https://clc.to/tvpmD По рекламе: @proglib_adv Для обратной связи: @proglibrary_feeedback_bot Наши каналы: https://t.me/proglibrary/9197

إظهار المزيد
7 429
المشتركون
-324 ساعات
-167 أيام
+930 أيام
أرشيف المشاركات
Может ли стек горутины быть ограниченным по памяти или он растёт сколько угодно По умолчанию максимальный размер стека горутины — 1 ГБ на 64-битных системах. Это задаётся рантаймом Go и контролируется переменной runtime/debug.SetMaxStack или просто константой в рантайме.
import "runtime/debug"

debug.SetMaxStack(512 * 1024 * 1024) // установить лимит 512 МБ
Если горутина превысит этот лимит, то будет паника. 🐸 Библиотека Go для собеса

Как работает механизм избирательного пробуждения горутин в sync.Cond на уровне структуры notifyList notifyList содержит два счётчика: wait (сколько горутин встало в очередь) и notify (сколько горутин уже получили сигнал). Когда горутина вызывает Wait(), она атомарно получает текущий тикет из wait, инкрементирует wait и засыпает. Тикет — это её уникальный порядковый номер в очереди. При вызове Signal() (который внутри вызывает notifyListNotifyOne) атомарно инкрементируется notify. Затем метод проходит по связанному списку горутин и ищет ту, чей тикет равен новому значению notify - 1. Именно она и пробуждается через goready. Такой подход гарантирует строгий FIFO-порядок пробуждения: горутины получают управление в том же порядке, в котором вставали в очередь. Это исключает голодание и делает поведение sync.Cond предсказуемым при конкурентном доступе. 🐸 Библиотека Go для собеса

Когда пригодится default в select default в Go превращает select в неблокирующую операцию. Если ни один канал не готов, выполнение сразу идёт дальше, без ожидания. Где это полезно: Таймауты и поллинг — мгновенно возвращаем управление, если данные ещё не пришли, вместо того чтобы зависнуть в ожидании. Event loopdefault берёт на себя фоновые задачи и проверки состояния, пока основной поток не простаивает в блокировке. 🐸 Библиотека Go для собеса

Какие состояния есть у горутины У горутины есть два концептуальных состояния, про которые обычно и спрашивают на собесах, и несколько внутренних статусов рантайма. Чаще всего говорят о двух состояниях живой горутины: Работает — выполняет код, либо спит через time.Sleep, ожидает системный вызов/сеть, но с точки зрения модели конкурентности она считается работающей.​ Заблокирована — стоит на ожидании, например, по WaitGroup.Wait, чтению/записи в канал, мьютексу и не может продолжать выполнение, пока условие не выполнится. Новая горутина после go f() стартует в работающем состоянии и может переходить в блокировку и обратно. Внутренние статусы рантайма Go _Gidle — горутина аллоцирована, но ещё не проинициализирована. _Grunnable — готова к запуску, лежит в локальной или глобальной очереди планировщика. _Grunning — сейчас исполняет пользовательский код на привязанном M и P. _Gwaiting — заблокирована рантаймом, ожидает в очереди канала, мьютекс, GC, и не стоит в run-queue. _Gdead — помечена как неиспользуемая, код не выполняет, стек может отсутствовать; объект может быть переиспользован под новую горутину. _Gcopystack — в процессе реаллокации стека, код не выполняется. _Gpreempted — принудительно остановлена из-за тайм-слайса и будет переведена в обычное ожидание и затем в runnable. 🐸 Библиотека Go для собеса

Как синхронизировать мапу Стандартная мапа в Go не потокобезопасна — при одновременном доступе из нескольких горутин без синхронизации неизбежен race condition. Есть три основных подхода: sync.Mutex / sync.RWMutex — самый распространённый способ. Mutex даёт полное взаимоисключение: только одна горутина работает с мапой в каждый момент. RWMutex умнее: несколько горутин могут читать одновременно, блокировка на запись возникает только при изменениях. Каналы — горутины отправляют запросы на чтение или запись через канал, который обрабатывает один воркер. Доступ строго последовательный, но реализация сложнее и производительность ниже. Шардирование — мапа делится на несколько независимых сегментов, каждый со своим мьютексом. Горутины конкурируют только внутри одного шарда, что заметно снижает contention при высокой нагрузке. Также стоит помнить про sync.Map из стандартной библиотеки — она подходит для случаев, когда ключи пишутся один раз, а читаются многократно. 🐸 Библиотека Go для собеса

Последний шанс: 3 курса по цене 1 и запуск AI-агентов в продакшн Архитектурные вопросы на Go-собеседованиях всё чаще касаются высоких нагрузок и ИИ. Как маршрутизировать мультиагентные системы под нагрузкой, контролировать затраты на токены и не нарушить 152-ФЗ? Обновлённая программа делает упор на жёсткий инжиниринг и вывод в прод. Вы научитесь строить ReAct-циклы, работать с LangGraph и AutoGen, внедрять продвинутый RAG, протоколы MCP и AgentOps. Все ключевые навыки в одном месте: измеримость систем, time-travel дебаггинг, управление браузером, human-in-the-loop и развёртывание в закрытых контурах. Почему нельзя откладывать: — масштабная акция «3 курса по цене 1» сгорает уже сегодня; — промокод Agent на скидку 10 000 рублей действует последние часы; — сразу после оформления открываются материалы для подготовки — начать учиться можно прямо сейчас. Забронировать место на курсе и забрать бонусы до конца дня

Чем отличается реляционная БД от нереляционной Реляционная или SQL — это база, где данные хранятся в таблицах, между которыми есть связи через внешние ключи. Примеры: PostgreSQL, MySQL, SQLite. Нереляционная или NoSQL — данные хранятся в других форматах: документы (MongoDB), ключ-значение (Redis), колонки (Cassandra), графы (Neo4j). Схема гибкая или вообще отсутствует, данные часто денормализованы. Основные отличия, которые важны на практике: реляционные базы дают полный ACID и хорошо работают со сложными связями между данными, но масштабируются преимущественно вертикально. NoSQL проще масштабировать горизонтально, они быстрее на простых операциях чтения, но жертвуют консистентностью — там eventual consistency, то есть данные не гарантированно консистентны прямо сейчас, но в конечном счёте все ноды синхронизируются и придут к одному состоянию. 🐸 Библиотека Go для собеса

Чем отличается реляционная БД от нереляционной Реляционная или SQL — это база, где данные хранятся в таблицах, между которыми есть связи через внешние ключи. Примеры: PostgreSQL, MySQL, SQLite. Нереляционная или NoSQL — данные хранятся в других форматах: документы (MongoDB), ключ-значение (Redis), колонки (Cassandra), графы (Neo4j). Схема гибкая или вообще отсутствует, данные часто денормализованы. Основные отличия, которые важны на практике: реляционные базы дают полный ACID и хорошо работают со сложными связями между данными, но масштабируются преимущественно вертикально. NoSQL проще масштабировать горизонтально, они быстрее на простых операциях чтения, но жертвуют консистентностью — там eventual consistency, то есть данные не гарантированно консистентны прямо сейчас, но в конечном счёте все ноды синхронизируются и придут к одному состоянию. 🐸 Библиотека Go для собеса

Может ли горутина общаться сама с собой через канал Да, но только через буферизированный канал. Когда горутина пишет в буферизированный канал, она не блокируется — если в буфере есть место, она записала значение и пошла дальше. Потом она же может вернуться и прочитать это значение из того же канала.
func main() {
    ch := make(chan int, 1) // буфер = 1

    ch <- 42       // записали, не заблокировались
    val := <-ch    // прочитали
    fmt.Println(val) // 42
}
Почему с небуферизированным не работает: Небуферизированный канал требует, чтобы отправитель и получатель были готовы одновременно. Если горутина попытается записать — она заблокируется и будет ждать читателя. Но читателя нет, потому что это та же самая горутина. Получается дедлок.
func main() {
    ch := make(chan int) // без буфера

    ch <- 42      // горутина блокируется здесь навсегда
    val := <-ch   // до этой строки никогда не дойдёт
    fmt.Println(val)
}
// fatal error: all goroutines are asleep - deadlock!
🐸 Библиотека Go для собеса

За год мы провели три потока курса по ИИ-агентам, а теперь запускаем масштабное обновление! В новом, четвёртом потоке мы учли все пожелания студентов, добавили большой блок про AgentOps и сместили фокус с базовых концепций на суровый инжиниринг. Рассказать про устройство горутин на собеседовании легко, а вот заставить ИИ-агента стабильно работать под высокой нагрузкой в проде и не сливать бюджет — задача со звёздочкой. В программе: — практика с первого занятия: Jupyter-ноутбуки с автопроверкой; — оркестрация в LangGraph: human-in-the-loop и механизм time-travel; — продвинутый RAG для продакшена и парсинг сложных документов; — контроль экономики агентов: маршрутизация и кеширование запросов; — развёртывание локальных опенсорс-моделей с соблюдением 152-ФЗ. В честь старта продаж действует спецпредложение: 3 курса по цене 1 (два дополнительных курса в подарок). Доступ к материалам для предварительной подготовки откроется сразу после оплаты. По промокоду Agent забирайте скидку 10 000 ₽ (89 000 ₽ вместо 99 000 ₽). Успейте занять место до 28 февраля! 👉 Присоединиться к четвёртому потоку и вывести агентов в прод

Если у типа методов больше, чем в интерфейсе — удовлетворяет ли он этому интерфейсу Тип реализует интерфейс, если имеет все методы интерфейса с правильными сигнатурами. Дополнительные методы — не проблема, их просто игнорирует. 🐸 Библиотека Go для собеса

В чём разница слайса байт и слайса рун []byte — слайс байт, каждый элемент 8 бит, аналог uint8. Это просто сырые данные, никак не привязанные к Unicode. []rune — слайс рун, каждый элемент 32 бита, аналог int32. Каждая руна — символ Unicode. Пусть есть строка s := "Привет, мир!" в UTF-8. • []byte(s) даёт байты UTF‑8: один символ может занимать 1–4 байта. Русские буквы занимают по 2 байта. • []rune(s) даёт последовательность символов Unicode: «П», «р», «и» … по одному элементу на символ, каждый элемент 4 байта. Используем []byte, когда: • Работаем с протоколами/сетевыми пакетами/файлами, где важны именно байты, а не текст. • Нужно эффективно модифицировать буфер без лишних аллокаций.​ Используем []rune, когда: • Нужно итерироваться по символам Unicode, например, обрезать строку по символам, делать toUpper/toLower посимвольно. • Важно корректно обрабатывать не-ASCII: кириллицу, эмодзи и т.п. 🐸 Библиотека Go для собеса

Go можно скомпилировать в бинарник, который запустится в пустом образе Docker, какие у этого есть подводные камни • нет sh, bash — нельзя зайти внутрь для дебага • нет ca-certificates — HTTPS-запросы упадут с ошибкой x509 • нет /etc/passwd — если приложение читает пользователей системы, будет проблема • нет tzdata — time.LoadLocation("Europe/Moscow") вернёт ошибку Это всё нужно копировать вручную:
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
🐸 Библиотека Go для собеса

В чём принципиальная разница между panic и os.Exit panic разворачивает стек, вызывает все defer и может быть перехвачена через recover, при этом процесс завершится с кодом 2, если не перехватить. os.Exit мгновенно завершает процесс с указанным кодом, не выполняет defer и ничего не даёт перехватить. 🐸 Библиотека Go для собеса

Можно ли обработать несколько ошибок за раз Да, с Go 1.20 — через errors.Join. Функция объединяет несколько ошибок в одну агрегированную, где каждая вложенная остаётся доступна через errors.Is и errors.As. Если хотя бы одна из переданных ошибок не nil — результат тоже будет не nil. 🐸 Библиотека Go для собеса

Чем конкурентность отличается от параллельности Конкурентность — это когда система управляет несколькими задачами, но не обязательно выполняет их одновременно. Задачи как бы делят между собой время: одна приостановилась — другая пошла, потом поменялись. Всё это может происходить даже на одном ядре. Параллельность — это когда задачи реально выполняются в один и тот же момент. Например, на многоядерном процессоре каждое ядро тащит свою задачу одновременно с остальными. Параллельность — это частный случай конкурентности. Можно быть конкурентным без параллельности, но параллельным без конкурентности — нет. 🐸 Библиотека Go для собеса

Почему реализация синглтона с простой проверкой if instance == nil не работает в конкурентной среде Несколько горутин могут одновременно пройти проверку instance == nil, поскольку эта операция не атомарна. Каждая из них создаст свой экземпляр, что нарушит принцип единственности объекта.
var instance *singleton

func GetInstance() *singleton {
    if instance == nil {  // Все горутины проходят здесь
        instance = &singleton{}  // Создается несколько экземпляров
    }
    return instance
}
Стандартная библиотека Go предоставляет sync.Once для безопасной ленивой инициализации:
package singleton

import "sync"

type singleton struct {
    // поля
}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}
🐸 Библиотека Go для собеса

Что случится с WaitGroup внутри горутины, если main завершится WaitGroup — это просто счётчик, он ничего не знает про main. Если main не вызывает wg.Wait(), он не блокируется и спокойно завершается. Все горутины убиваются вместе с процессом. 🐸 Библиотека Go для собеса

Приведите пример, когда может возникнуть race condition Классический пример — несколько горутин одновременно читают и пишут в одну переменную без синхронизации:
package main

import (
    "fmt"
    "sync"
)

func main() {
    counter := 0
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter++ // race condition!
        }()
    }

    wg.Wait()
    fmt.Println(counter) // ожидаем 1000, но получим что-то другое
}
Проблема в том, что counter++ — это не атомарная операция, а три шага: 1. значение 2. прибавить 1 3. записать результат Две горутины могут одновременно прочитать одно и то же значение, обе прибавят 1 и запишут одинаковый результат — одно из увеличений потеряется. Варианты исправления С мьютексом:
var mu sync.Mutex
mu.Lock()
counter++
mu.Unlock()
С атомарной операцией:
import "sync/atomic"
atomic.AddInt64(&counter, 1)
Обнаружить гонку можно с помощью встроенного детектора — флаг -race при запуске:
go run -race main.go
🐸 Библиотека Go для собеса

Зачем в канале используют пустую структуру Пустая структура в каналах используется для сигнализации между горутинами без передачи данных. chan struct{} сразу показывает, что канал нужен только для уведомлений, а не для данных. В отличие от chan bool, где значения true/false могут вызвать путаницу в интерпретации. Тип struct{} занимает 0 байт — Go оптимизирует такие структуры, не выделяя для них память. Передача сигнала происходит без копирования данных, что даёт небольшую, но оптимизацию. Практический пример:
done := make(chan struct{})

go func() {
    // работа горутины...
    close(done)  // сигнал "завершено"
}()

<-done  // ожидание сигнала
🐸 Библиотека Go для собеса