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

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

Kanalga Telegram’da o‘tish

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

Ko'proq ko'rsatish
7 430
Obunachilar
-124 soatlar
-137 kunlar
+730 kunlar
Postlar arxiv
В чем заключается принцип работы Escape analysis в Go Escape analysis — это техника компилятора Go, предназначенная для определения оптимального местоположения переменных: в стеке или в куче. Этот процесс критичен для повышения производительности программы и эффективного использования памяти. В Go нет прямого способа указать компилятору, где именно должна быть размещена переменная. Вместо этого это решение зависит от того, как написан ваш код. Компилятор анализирует область видимости и жизненный цикл переменных. Если переменная не выходит за пределы своей области видимости, она будет размещена на стеке. Это оптимально с точки зрения производительности, поскольку стек автоматически очищается, как только выполнение функции завершено. Если же переменная «выходит» — например, возвращается из функции или сохраняется в глобальной переменной — она будет размещена в куче, и за её освобождение будет отвечать сборщик мусора. ➡️ Переменная остается в рамках функции (стек):
func sum(a, b int) int {
    result := a + b 
    return result
}
В данном случае переменная result остается в области видимости функции, поэтому она будет размещена на стеке. ➡️ Переменная выходит за пределы функции (куча):
func newInt() *int {
    result := 42 
    return &result
}
Здесь переменная result выходит за пределы функции, потому что возвращается указатель на неё. Поэтому она будет размещена в куче. ➡️ Переменная сохраняется в глобальной переменной (куча):
var globalVar *int

func setGlobalVar() {
    x := 100  
    globalVar = &x
}
Переменная x сохраняется в глобальной переменной, и её размещение будет происходить в куче, так как она выходит за пределы локальной области видимости функции. Escape analysis помогает управлять памятью, решая, где лучше хранить переменные — в стеке или в куче. 🐸 Библиотека Go для собеса

⚡ Релиз easyoffer 2.0 — сайта по подготовке к IT собеседованиям! Разработку проекта поддержали 1600 айтишников, а суммарно на
Релиз easyoffer 2.0 — сайта по подготовке к IT собеседованиям! Разработку проекта поддержали 1600 айтишников, а суммарно на запуск было собрано 5 млн. руб. через краудфандинг. «Всё в одном» для тех, кто ищет работу в IT: 🟢Аналитика собесов на основе 4500+ реальных интервью 🟢Вопросы и задачи из собеседований с вероятностью встречи 🟢Примеры видео-ответов от Senior/Middle разработчиков 🟢Тренажеры для подготовки 🟢Автоотклики на вакансии и другое. В честь релиза первые 500 пользователей получат скидку 60% на годовой PRO-доступ ⚠ Что нужно сделать: 🔔 Подпишитесь на Telegram-канал проекта https://t.me/+UYkjii31QQozZjgy Там появится анонс релиза раньше, чем где-либо ещё. Вы успеете попасть в число первых 500 и получить максимальную выгоду. Реклама. ИП Кивайко Алексей Викторович, ИНН 532121460552. Erid 2VtzqvmGkoZ

Что представляют собой дженерики в языке Go и в чем их преимущества Дженерики в Go — это механизм, введенный в версии 1.18, который позволяет создавать функции и структуры данных, работающие с разными типами данных без явного указания этих типов при их объявлении. Дженерики позволяют избежать дублирования кода, предоставляя универсальные решения для множества задач. Это способствует производительности, так как исключает необходимость в преобразованиях типов, что могло бы привести к потерям времени на выполнение. ➡️ Преимущества: • Уменьшение дублирования кода с помощью дженериков можно создать одну функцию или структуру, которая будет работать с любыми типами данных. • Повышенная безопасность типов, дженерики предоставляют строгую типовую проверку, что предотвращает ошибки на этапе компиляции. • Увеличение производительности, избавление от преобразования типов позволяет улучшить скорость выполнения. ➡️ До дженериков До введения дженериков, разработчики использовали интерфейсы и преобразование типов для обеспечения гибкости. Однако это часто приводило к потере производительности и снижению безопасности типов. Например, для создания функции поиска минимального значения в срезе, нужно было писать отдельные реализации для каждого типа:
func MinInts(arr []int) int { /*...*/ }
func MinFloats(arr []float64) float64 { /*...*/ }
➡️ С дженериками Теперь с использованием дженериков можно создать одну универсальную функцию, которая работает с различными типами данных. Пример с функцией для нахождения минимального значения:
func Min[T comparable](arr []T) T { /*...*/ }
Здесь T — это параметр типа, а comparable указывает, что тип должен поддерживать операцию сравнения. ➡️ Пример с обобщенной структурой данных Допустим, вам нужно создать структуру данных типа очередь (Queue). Без дженериков:
type Queue struct {
    data []interface{}
}
Использование interface{} требует преобразования типов, что снижает производительность. С дженериками:
type Queue[T any] struct {
    data []T
}
Здесь T может быть любым типом, и нет необходимости в преобразованиях. Внедрение дженериков в Go получило положительный отклик среди большинства разработчиков, однако они подчеркивают, что важно применять этот механизм с осторожностью и продуманно. 🐸 Библиотека Go для собеса

Какова цель и принцип работы планировщика Go на базовом уровне Планировщик Go отвечает за эффективное управление горутинами и переключение контекста между ними, что позволяет максимально использовать ресурсы процессора. ➡️ Модель M:N — в Go используется модель M:N, при которой M горутин могут быть назначены на N потоках ОС. Это позволяет создавать тысячи горутин с минимальными накладными расходами, связанными с управлением реальными потоками операционной системы. ➡️ G, M и P: • G (goroutine) — горутина, выполняющаяся в рамках Go-программы • M (machine thread) — поток ОС, который выполняет горутины • P (processor) — контекст выполнения, который управляет очередью горутин и другими необходимыми данными. Обычно количество P соответствует числу ядер процессора. ➡️ Работа планировщика — во время выполнения горутины, она захватывает P и привязывается к M. Если горутина блокируется (например, из-за ожидания ввода/вывода), P освобождается и может быть назначен другой горутине, которая готова к выполнению. ➡️ Адаптация под нагрузку — планировщик динамически увеличивает или уменьшает количество потоков ОС (M) в зависимости от текущей нагрузки и блокировок. ➡️ Прерывание горутин — горутины могут быть прерваны планировщиком, чтобы освободить поток ОС для других горутин. Это гарантирует, что одна горутина не будет занимать процессорное время слишком долго, давая возможность другим горутинам быть выполненными. 🐸 Библиотека Go для собеса

Уже освоили базу Go, но хотите выйти на новый уровень и научиться работать с памятью напрямую? 📍 На открытом уроке «Пакет un
Уже освоили базу Go, но хотите выйти на новый уровень и научиться работать с памятью напрямую? 📍 На открытом уроке «Пакет unsafe. Godmode ON» 23 июля в 20:00 МСК мы покажем как небезопасные операции с памятью расширяют возможности Go-разработчика: от обхода ограничений type safety до повышения производительности. Представьте: вы разбираетесь, как устроена память внутри Go-объектов, вручную управляете указателями и получаете контроль над структурой данных. Вместе с экспертом вы протестируете кейсы, поймёте риски и преимущества. 👉 Регистрируйтесь и получите скидку на участие в большом курсе «Golang Developer. Professional»: https://clc.to/z2CZjg Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576

Как работает паттерн «Функциональные опции» в Go и как его можно реализовать Паттерн «Функциональные опции» предоставляет удобный и гибкий способ конфигурации структур в Go без раскрытия их внутренних полей. Это решение помогает создать расширяемые и легко поддерживаемые объекты, что особенно важно, если в будущем предполагаются изменения или добавление новых параметров. Этот подход идеален, когда необходимо предоставить дополнительные параметры конфигурации или в случае, когда структура может изменяться, не нарушая совместимость с предыдущими версиями кода. Он также широко используется в библиотеках, где важно сохранять совместимость с различными версиями. Пример без функциональных опций:
type Server struct {
    host     string
    port     int
    protocol string
}

func NewServer(host string, port int) *Server {
    return &Server{host: host, port: port, protocol: "http"}
}
Этот код создает сервер с параметрами host и port, но с изменениями в требованиях необходимо менять сигнатуру функции, что неудобно. Реализация функциональных опций 1️⃣ Определим тип для опций:
type ServerOption func(*Server)
2️⃣ Функция для изменения порта:
func WithPort(port int) ServerOption {
    return func(s *Server) {
        s.port = port
    }
}
3️⃣ Модифицируем функцию NewServer:
func NewServer(host string, opts ...ServerOption) *Server {
    server := &Server{host: host, port: 443, protocol: "https"}
    for _, opt := range opts {
        opt(server)
    }
    return server
}
Теперь можно гибко настроить параметры:
server1 := NewServer("localhost")               // с портом по умолчанию
server2 := NewServer("localhost", WithPort(8080)) // с портом 8080
Этот подход позволяет добавлять параметры без изменения кода и нарушений совместимости. 🐸 Библиотека Go для собеса

Какие правила happens-before действуют для атомарных операций из пакета sync/atomic Атомарная операция Store happens-before последующей атомарной операции Load той же переменной (при отсутствии иных атомарных модификаций между ними). Инициализация всех глобальных переменных happens-before старту функции main(), что гарантирует корректные первоначальные значения при запуске программы. 🐸 Библиотека Go для собеса

Go-сервис генерирует логи, но вы теряетесь в поиске и анализе? Хотите научиться строить быстрый и надёжный поиск по данным ми
Go-сервис генерирует логи, но вы теряетесь в поиске и анализе? Хотите научиться строить быстрый и надёжный поиск по данным микросервисов? 📅 17 июля в 20:00 (МСК) — открытый урок «Взаимодействие микросервиса на Go и Elasticsearch» Разберём: ▪️ Архитектуру микросервисов на Go и ключевые нюансы ▪️ Базовые принципы работы с Elasticsearch ▪️ Интеграцию Go-сервиса с Elasticsearch для логирования и поиска ▪️ Настройку индексов, фильтрацию и агрегации Представьте, что вы разворачиваете микросервис, подключаете его к Elasticsearch, логируете события и выдаёте мгновенный полнотекстовый поиск по данным. Ваш сервис становится прозрачным и управляемым. 🚀 Регистрируйтесь на урок «Взаимодействие микросервиса на Go и Elasticsearch»: https://clc.to/sTcjRQ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

Как Redis реализует сохранение данных на диск Redis поддерживает два механизма сохранения данных: Redis Database Dump, который сохраняет данные в момент времени, и Append-Only File, который записывает каждую операцию записи. Эти два метода могут использоваться вместе для достижения компромисса между производительностью и надежностью. 🐸 Библиотека Go для собеса

Как вам вопросы прошедшей недели Оцените их по шкале 🔥,❤️,👍,😢, 🥱, где 🔥 — это супер, а 🥱 — это скучно. Также приветствуется фидбек в комментариях. 🐸 Библиотека Go для собеса

Что такое отношение happens-before в модели памяти Go Отношение happens-before гарантирует упорядоченность и видимость операций между разными горутинами. Если операция A happens-before операции B, то все записанные до A значения памяти гарантированно будут видны при выполнении B. 🐸 Библиотека Go для собеса

Как реализовать приоритет между каналами, если select выбирает случайно Одно из важных свойств select в Go — рандомность выбора, если сразу несколько каналов готовы к операции.
select {
case msg := <-ch1:
    fmt.Println("ch1:", msg)
case msg := <-ch2:
    fmt.Println("ch2:", msg)
}
Если и ch1, и ch2 доступны — Go случайным образом выберет один case. Это исключает жёсткий приоритет каналов и распределяет нагрузку справедливо (не детерминированно). Это нужно для предотвращения "голодания" менее приоритетных каналов, также это позволяет реализовать честные очереди и worker pool без ручного балансировщика 🐸 Библиотека Go для собеса

Можно ли сделать Singleton тестируемым без нарушения его природы Singleton может мешать тестированию, так как его состояние живёт весь runtime. Но есть обходные пути: 1️⃣ Вынос в интерфейс
type Storage interface {
    Get(key string) string
}
2️⃣ Инъекция зависимости через параметр
func ProcessData(s Storage) { ... }
3️⃣ Сброс состояния Singleton (в тестах)
// В тестовых сборках
func resetSingleton() {
    instance = nil
    once = sync.Once{}
}
❕Warning: сброс Singleton — антипаттерн, но допустим в юнит-тестах 🐸 Библиотека Go для собеса

Зачем использовать select, если можно просто читать из канала В Go оператор select — это конкурентный аналог switch, предназначенный исключительно для работы с каналами. С его помощью можно: • Ждать сразу несколько операций с каналами (чтение/запись) • Управлять конкурентными потоками без блокировок • Не блокироваться, если добавить default ветку
select {
case msg := <-ch1:
    fmt.Println("Received from ch1:", msg)
case ch2 <- 42:
    fmt.Println("Sent 42 to ch2")
default:
    fmt.Println("Nothing ready")
}
Если ch1 или ch2 готовы — будет выполнен соответствующий case. Если ни один канал не активен — выполняется default, и select не блокирует выполнение. 🐸 Библиотека Go для собеса

❓ Какой способ инициализации Singleton в Go выбрать: sync.Mutex, sync.Once или init() 🐸 Библиотека Go для собеса
Какой способ инициализации Singleton в Go выбрать: sync.Mutex, sync.Once или init() 🐸 Библиотека Go для собеса

Как правильно использовать context в юнит-тестах context — мощный инструмент, но в тестах он может мешать, особенно если используется без ограничений по времени или отмене. Чтобы избежать зависаний и утечек в юнит-тестах, всегда создавайте context с таймаутом:
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
Это гарантирует: Тест не будет висеть бесконечно Ресурсы будут высвобождены Горутины получат сигнал на завершение Если ваш тест зависает — это может говорить о том, что где-то в коде игнорируется ctx.Done() 🐸 Библиотека Go для собеса

❓ Почему инициализация через init() не считается ленивой Иногда экземпляр Singleton можно создавать заранее, при старте пакета. В Go для этого существует функция init(). Singleton с init() в действии
package main

import (
    "fmt"
)

type singleton struct {
    config string
}

var instance *singleton

func init() {
    instance = &singleton{config: "preloaded"}
}

func GetInstance() *singleton {
    return instance
}

func main() {
    s := GetInstance()
    fmt.Println("Singleton config:", s.config)
}
➡️ Особенности подхода: • Инициализация происходит один раз, до выполнения main() — автоматически • Синхронизация не требуется init() вызывается в однопоточном контексте • Порядок инициализации между пакетами гарантирован Go-рантаймом ➡️ Подходит для простых случаев: Объект всегда нужен в программе • Конфигурация не зависит от внешнего ввода • Важна простота, а не гибкость ➡️ Недостатки Нарушает ленивую загрузку — объект создаётся даже если не используется Затрудняет подмену или настройку из внешнего источника (например, через флаги, файлы, ENV) Может ограничить тестируемость и повторную инициализацию 🐸 Библиотека Go для собеса

Как вам вопросы прошедшей недели Оцените их по шкале 🔥,❤️,👍,😢, 🥱, где 🔥 — это супер, а 🥱 — это скучно. Также приветствуется фидбек в комментариях. 🐸 Библиотека Go для собеса

Как правильно использовать ctx.Done() внутри горутин и что произойдёт, если этого не делать Контекст используется как сигнальный канал. Правильно обрабатывать ctx.Done() нужно всегда, особенно в горутинах
go func(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("goroutine stopped:", ctx.Err())
            return
        case <-time.After(1 * time.Second):
            fmt.Println("working...")
        }
    }
}(ctx)
Если не слушать ctx.Done(), вы получите: • Утечки горутин • Зависание приложения • Недетерминированное поведение при отмене 🐸 Библиотека Go для собеса

Зачем в реализации Singleton использовать sync.Once, если уже есть мьютексы Если вы хотите простую, лаконичную и безопасную реализацию — используйте sync.Once
package main

import (
    "fmt"
    "sync"
)

type singleton struct {
    config string
}

var (
    once     sync.Once
    instance *singleton
)

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{config: "default"}
    })
    return instance
}

func main() {
    s := GetInstance()
    fmt.Println("Singleton config:", s.config)
}
sync.Once гарантирует, что переданная функция будет выполнена строго один раз, даже если GetInstance вызывается из множества горутин. ➕Потокобезопасность на уровне стандартной библиотеки Лаконичность кода Не требует мьютексов или ручных проверок ➖Не подходит, если вам нужно сбрасывать или пересоздавать Singleton (например, в тестах) Once работает только один раз за весь жизненный цикл приложения 🐸 Библиотека Go для собеса