Библиотека Go для собеса | вопросы с собеседований
6.89K subscribers
222 photos
6 videos
1 file
430 links
Вопросы с собеседований по Go и ответы на них.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/0b524a15

Для обратной связи: @proglibrary_feeedback_bot

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
💬 Что из себя представляет паттерн Fan-In (Мультиплексор), применяемый в облачной разработке?

Паттерн Fan-In мультиплексирует несколько входных каналов в один выходной канал.

В сервисах, имеющих несколько рабочих процессов, генерирующих результаты, бывает полезно объединить эти результаты в один общий поток. В таких случаях используется шаблон Мультиплексор, позволяющий читать данные из нескольких входных каналов и передавать их в один целевой канал.

📌 Компоненты:
Sources — набор из одного или нескольких входных каналов одного типа, которые может принять Funnel.
Destination — выходной канал того же типа, что и каналы в наборе Sources. Создается функцией Funnel.
Funnel — принимает набор Sources и сразу же возвращает Destination. Любые данные, поступающие из каналов в Sources, передаются в Destination.

📌 Реализация:
Funnel — функция, которая принимает до N входных каналов (Sources). Для каждого входного канала в Sources функция Funnel запускает отдельную горутину, читающую значения из назначенного ей канала и передающую их в выходной канал, общий для всех горутин (Destination).

Функция Funnel принимает переменное число аргументов — ноль или более каналов некоторого типа (int в нашем примере):

func Funnel(sources ...<-chan int) <-chan int {
dest := make(chan int) // Общий выходной канал

var wg sync.WaitGroup // Для автоматического закрытия dest, когда закроются все входящие каналы sources

wg.Add(len(sources)) // Установить размер WaitGroup

for _, ch := range sources { // Запуск горутины для каждого входного канала
go func(c <-chan int) {
defer wg.Done() // Уведомить WaitGroup, когда c закроется

for n := range c {
dest <- n
}
}(ch)
}

go func() { // Запустить горутину, которая закроет dest после закрытия всех входных каналов
wg.Wait()
close(dest)
}()

return dest
}


Для каждого входного канала Funnel запускает специальную горутину, которая читает значения из назначенного ей канала и пересылает их в dest, общий выходной канал для всех горутин.

📌 Использование функции Funnel:

func main() {
sources := make([]<-chan int, 0) // Создать пустой срез с каналами

for i := 0; i < 3; i++ {
ch := make(chan int)
sources = append(sources, ch) // Создать канал; добавить в срез sources
go func() { // Запустить горутину для каждого
defer close(ch) // Закрыть канал по завершении горутины
for i := 1; i <= 5; i++ {
ch <- i
time.Sleep(time.Second) }
}()
}

dest := Funnel(sources...)
for d := range dest {
fmt.Println(d)
}
}
👍12
💬 Как реализовать аргументы командной строки в Go?

Go предоставляет пакет стандартной библиотеки flag, который является мощным инструментом для обработки аргументов командной строки. Он позволяет легко определять и парсить аргументы.

🔸 Основные функции: flag.String, flag.Int, flag.Bool и т. д., используются для определения ожидаемых аргументов командной строки с указанием их типа.

🔸 Парсинг: функция flag.Parse() используется для анализа аргументов командной строки и присвоения их соответствующим переменным.

📌 Простой пример:

package main

import (
"flag"
"fmt"
)

func main() {
// Определение аргументов
host := flag.String("host", "localhost", "a string var")
port := flag.Int("port", 8080, "an int var")
verbose := flag.Bool("verbose", false, "a bool var")

// Парсинг аргументов
flag.Parse()

// Использование аргументов
fmt.Printf("Host: %s, Port: %d, Verbose: %t\n", *host, *port, *verbose)
}


📌 Основные преимущества:

🔸 Библиотека позволяет легко добавлять и изменять параметры командной строки, что делает код гибким и легко адаптируемым к изменениям требований.

🔸 Использование указателей для аргументов (flag.String, flag.Int и т. д.) обеспечивает прямой доступ к значениям после парсинга.

🔸 Библиотека позволяет задавать значения по умолчанию и описания для каждого аргумента, что улучшает понимание и использование командной строки.

🔸 Для более сложных сценариев использования аргументов командной строки можно использовать сторонние библиотеки, такие как cobra или urfave/cli, которые предоставляют дополнительные возможности и улучшенный синтаксис.
🔥71
🧑‍💻 Статьи для IT: как объяснять и распространять значимые идеи

Напоминаем, что у нас есть бесплатный курс для всех, кто хочет научиться интересно писать — о программировании и в целом.

Что: семь модулей, посвященных написанию, редактированию, иллюстрированию и распространению публикаций.

Для кого: для авторов, копирайтеров и просто программистов, которые хотят научиться интересно рассказывать о своих проектах.

👉Материалы регулярно дополняются, обновляются и корректируются. А еще мы отвечаем на все учебные вопросы в комментариях курса.
👍2
💬 Что из себя представляют методы типа в Go?

Метод типа (методы на типах) — это функция, которая привязана к определенному типу данных. Методы типов на самом деле являются функциями, однако определяются и используются немного по-другому.

Функционал методов типа добавляет в Go некоторые возможности ООП. Кроме того, методы типа нужны для работы интерфейсов.

📌 Создание методов типа

Предположим, что мы хотим выполнить вычисления с помощью матриц 2×2. Самым естественным способом реализации является определение нового типа данных и методов типа для сложения, вычитания и умножения матриц 2×2 с использованием этого нового типа данных.

Имея тип данных ar2x2, мы можем создать для него метод типа functionName следующим образом:

func (a ar2x2) FunctionName(parameters) <return values> {
...
}


(a ar2x2) — то, что делает функцию functionName() методом типа, поскольку связывает ее с типом данных ar2x2. Никакой другой тип данных не сможет использовать эту функцию. Однако мы можем без проблем реализовать functionName() для других типов данных или в виде обычной функции. Если у нас есть переменная ar2x2 с именем varAr, мы можем вызвать functionName() как varAr.functionName(...), что выглядит как выбор поля структуры.

Мы не обязаны использовать методы типа, если не хотим этого. Фактически любой метод типа может быть переписан как обычная функция. Следовательно, functionName() можно переписать так:

func FunctionName(a ar2x2, parameters...) <return values> {
...
}
🔥6
💬 Можно ли давать имена возвращаемым значениям функции Go?

В отличие от C, Go позволяет называть возвращаемые значения Go-функции. Кроме того, когда такая функция имеет оператор return без каких-либо аргументов, она автоматически возвращает текущее значение каждого именованного возвращаемого значения в том порядке, в котором они были объявлены в сигнатуре.

Следующая функция включена в namedReturn.go:

func minMax(x, y int) (min, max int) {
if x > y {
min = y
max = x
return min, max


Здесь оператор return возвращает значения, сохраненные в переменных min и max. Обе переменные определены в сигнатуре, а не в теле функции.

    }

min = x
max = y
return
}


А здесь он эквивалентен return min, max. В его основе — сигнатура функции и использование именованных возвращаемых значений. При выполнении namedReturn.go мы получаем такой вывод:

$ go run namedReturn.go 1 -2
-2 1
-2 1
👍10🥱5🌚1
💬 Что из себя представляет буферизованный и небуферизованный файловый ввод-вывод?

🔸 Буферизованный файловый ввод-вывод — это использование буфера для временного хранения данных перед чтением или записью. Таким образом, вместо того чтобы читать файл побайтово, мы читаем сразу множество данных. Мы помещаем данные в буфер и ожидаем, пока кто-нибудь их не прочитает желаемым образом.

🔸 Небуферизованный файловый ввод-вывод: буфер для временного хранения данных не используется перед их фактическим чтением или записью‚ что может повлиять на производительность.

📌 Когда какой использовать?

При работе с критически важными данными небуферизованный файловый ввод-вывод, как правило, является лучшим выбором, поскольку буферизованное чтение может привести к использованию устаревших данных, а небуферизованная запись — к потере данных в случае сбоя. Однако в большинстве случаев однозначного ответа на этот вопрос нет.
👍6
🧠Чему вы бы хотели научиться?

Расскажите нам о ваших пожеланиях: какие навыки вы хотели бы прокачать в ближайшее время или какую профессию хотели бы приобрести?

За прохождение опроса вы получите промокод на скидку 15% на все наши курсы до конца 2024 года.

👉Опрос по ссылке👈
💬 Опишите популярный кейс использования /dev/random в Go и представьте простой пример.

Назначение /dev/random — генерация случайных данных, которые можно использовать для тестирования программ или в качестве исходных данных для генератора случайных чисел.

📌 Простой пример:

package main
import (
"encoding/binary"
"fmt"
"os"
)

func main() {
f, err := os.Open("/dev/random")
defer f.Close()
if err != nil {
fmt.Println(err)
return }
var seed int64
binary.Read(f, binary.LittleEndian, &seed)
fmt.Println("Seed:", seed)
}


Есть два представления‚ прямой и обратный порядок, которые имеют отношение к порядку байтов во внутреннем представлении. В нашем случае мы используем прямой порядок. Порядок здесь имеет отношение к тому‚ как различные вычислительные системы упорядочивают несколько байтов информации.

💡Реальный пример порядка — это то, как в разных языках по-разному читается текст.

💡В представлении с обратным порядком байты считываются слева направо, в то время как прямой порядок подразумевает считывание справа налево. Для значения 0x01234567, хранение которого требует четырех байтов, представление с обратным порядком равно 01|23|45|67‚ тогда как прямой порядок выглядит как 67|45|23|01.
1👍1
💬 Каково основное назначение и чем полезен пакет runtime/coverage, представленный в Go 1.20?

Новый пакет предоставляет API для записи данных профиля покрытия во время выполнения для долгосрочных и/или серверных программ, которые не завершаются стандартным образом через os.Exit().

Это позволяет собирать данные о покрытии кода тестами в реальном времени, что особенно полезно для мониторинга и улучшения качества кода в продакшн окружениях, где традиционные методы сбора данных о покрытии могут быть неэффективны или неприменимы.
🔥4
🏃 Самоучитель по Go для начинающих. Часть 4. Переменные. Типы данных и их преобразования. Основные операторы.

В этом уроке мы изучим фундаментальные понятия в программировании: переменные, типы данных и операторы, а также научимся преобразовывать один тип данных в другой.

👉 Читать статью
👉 Часть 1
👉 Часть 2
👉 Часть 3
💬 Опишите основные возможности и пример использования пакета io/fs, добавленного в Go 1.16.

io/fs предлагает интерфейс файловой системы FS, доступный только для чтения. embed.FS реализует интерфейс fs.FS, что дает возможность пользоваться некоторыми функциональными возможностями пакета io/fs.

Следовательно, наши приложения могут создавать собственные внутренние файловые системы и работать с их файлами.

📌 Простой пример:

func list(f embed.FS) error {
return fs.WalkDir(f, ".", walkFunction)
}

func walkFunction(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("Path=%q, isDir=%v\n", path, d.IsDir())
return nil
}

func extract(f embed.FS, filepath string) ([]byte, error) {
s, err := fs.ReadFile(f, filepath)
if err != nil {
return nil, err
}
return s, nil
}


Функция ReadFile() используется для извлечения файла, который идентифицируется по пути к файлу, из файловой системы embed.FS в виде байтового среза, возвращаемого из функции extract().

func walkSearch(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.Name() == searchString {


searchString — это глобальная переменная, которая содержит строку поиска. Когда совпадение найдено, соответствующий путь выводится на экран.

        fileInfo, err := fs.Stat(f, path)
if err != nil {
return err
}
fmt.Println("Found", path, "with size", fileInfo.Size())
return nil
}


Прежде чем выводить совпадение, мы вызываем fs.Stat() с целью получить о нем более подробную информацию.

    return nil 
}
👍8
💬 Что такое атомарная операция и для чего предназначен пакет atomic?

Атомарная операция выполняется за один шаг относительно других потоков или, в контексте Go, других горутин. Это означает, что атомарную операцию нельзя прервать в середине ее работы.

Стандартная библиотека Go содержит пакет atomic, который в некоторых простых случаях может помочь избежать использования мьютекса. С помощью него мы получаем доступ к атомарным счетчикам из нескольких горутин, не имея проблем с синхронизацией и не беспокоясь о race condition.

Как показано в примере ниже, при использовании атомарной переменной во избежание race condition все операции чтения и записи атомарной переменной должны выполняться с помощью функций, предоставляемых пакетом atomic.

package main
import (
"fmt"
"sync"
"sync/atomic"
)

type atomCounter struct {
val int64 }


Это структура для хранения требуемой атомарной переменной int64.

func (c *atomCounter) Value() int64 {
return atomic.LoadInt64(&c.val)
}


Это вспомогательная функция, которая возвращает текущее значение атомарной переменной int64, используя atomic.LoadInt64().

func main() {
X := 100
Y := 4
var waitGroup sync.WaitGroup
counter := atomCounter{}
for i := 0; i < X; i++ {


Мы создаем множество горутин, которые изменяют общую переменную. Благодаря использованию пакета atomic для работы с общей переменной мы получаем простой способ избежать race condition при изменении ее значения.

        waitGroup.Add(1)
go func(no int) {
defer waitGroup.Done()
for i := 0; i < Y; i++ {
atomic.AddInt64(&counter.val, 1)
}


Функция atomic.AddInt64() безопасно изменяет значение поля val структуры counter.

            }(i) 
}

waitGroup.Wait()
fmt.Println(counter.Value())
}
👍143🔥1
💬 Что такое Duck Typing и как эта концепция применяется в Go?

Duck Typing — это концепция в программировании, которая гласит, что важно не то, какой тип у объекта, а то, какие операции с ним можно выполнять. Её суть заключается в фразе: «Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, утка».

В контексте программирования это означает, что объекты считаются совместимыми, если они имеют необходимый набор методов и свойств, независимо от того, к какому конкретно классу или типу они принадлежат.

В Go Duck Typing реализуется через интерфейсы. Интерфейс в Go определяет набор методов, но не указывает, какие структуры или типы должны эти методы реализовывать. Любой тип, который реализует все методы интерфейса, автоматически считается реализующим этот интерфейс. Это означает, что в Go нет необходимости явно указывать, что конкретный тип реализует определенный интерфейс.
👍154🥱2
💬 Что из себя представляет пакет semaphore в Go?

Семафор — это конструкция, которая может ограничивать или контролировать доступ к общему ресурсу. В контексте Go, семафор может ограничить доступ горутин к общему ресурсу, но первоначально семафоры использовались для ограничения доступа к потокам.

📌 Немного практики

Семафоры могут иметь веса, которые задают максимальное количество потоков или горутин, получающих доступ к ресурсу.
Процесс поддерживается с помощью методов Acquire() и Release(), определенных следующим образом:

func (s *Weighted) Acquire(ctx context.Context, n int64) error
func (s *Weighted) Release(n int64)


Второй параметр Acquire() определяет вес семафора.

package main

import (
"context"
"fmt"
"os"
"strconv"
"time"
"golang.org/x/sync/semaphore"
)

var Workers = 4


Эта переменная определяет максимальное количество горутин, которые могут быть выполнены данной программой.

var sem = semaphore.NewWeighted(int64(Workers))


Здесь мы определяем семафор с весом, идентичным максимальному количеству горутин, которые могут выполняться одновременно. Это означает, что получать семафор одновременно могут не более чем Workers горутин.

func worker(n int) int {
square := n * n
time.Sleep(time.Second)
return square
}


Функция worker() выполняется как часть горутины. Однако поскольку мы используем семафор, нет необходимости возвращать результаты в канал.

func main() {
if len(os.Args) != 2 {
fmt.Println("Need #jobs!")
return
}

nJobs, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println(err)
return
}


Считываем количество заданий, которые хотим запустить.

    // где хранить результаты
var results = make([]int, nJobs)
// требуется для Acquire()
ctx := context.TODO()

for i := range results {
err = sem.Acquire(ctx, 1)
if err != nil {
fmt.Println("Cannot acquire semaphore:", err)
break
}


Получаем семафор столько раз, сколько заданий определено nJobs. Если nJobs больше, чем Workers, то вызов Acquire() будет заблокирован и дождется вызовов Release() для разблокировки.

            go func(i int) {
defer sem.Release(1)
temp := worker(i)
results[i] = temp
}(i)
}


Запускаем горутины, которые выполняют эту задачу, и записываем результаты в срез results. Поскольку каждая горутина записывает данные в свой элемент среза, никаких race condition нет.

    err = sem.Acquire(ctx, int64(Workers))
if err != nil {
fmt.Println(err)
}


Получаем все токены таким образом, чтобы вызов sem.Acquire() блокировался до тех пор, пока все рабочие процесссы/горутины не завершат работу. Функционально это похоже на вызов Wait().

    for k, v := range results {
fmt.Println(k, "->", v)
}
}
🤔4🔥31
💬 Как сообщить компилятору Go, что наш тип реализует интерфейс?

В Go, интерфейсы реализуются неявно. Это означает, что нам не нужно явно указывать, что наш тип реализует интерфейс.

Вместо этого, если наш тип определяет все методы, которые присутствуют в интерфейсе, то он считается его реализующим.

📌Простой пример:

Допустим, у нас есть следующий интерфейс:

type Speaker interface {
Speak() string
}


и тип Person:

type Person struct {
Name string
}

func (p Person) Speak() string {
return "My name is " + p.Name
}


Так как Person определяет метод Speak(), который присутствует в интерфейсе Speaker, Person автоматически реализует интерфейс Speaker. Нет необходимости в дополнительном коде или объявлении для подтверждения этого.
11👍7🌚1
💬 Что такое tight coupling в контексте Go?

В контексте Go, tight coupling обозначает ситуацию, когда компоненты, такие как структуры, интерфейсы, пакеты или функции, сильно зависят друг от друга.

📌 Это проявляется в нескольких аспектах:

1. Прямая зависимость между структурами: если одна структура в Go содержит или прямо ссылается на другую структуру и прямо использует её методы и свойства, это создаёт tight coupling. Изменения в одной структуре могут потребовать изменений в другой.

2. Использование глобальных переменных: частое использование глобальных переменных, которые доступны во многих частях программы, приводит к tight coupling, т. к. различные части программы становятся зависимыми от глобального состояния.

3. Отсутствие интерфейсов: в Go интерфейсы используются для создания слабой связанности. Если код прямо зависит от конкретных реализаций, а не от абстракций (например, интерфейсов), это увеличивает степень связанности.

4. Тесная интеграция между пакетами: когда один пакет в Go импортирует множество других пакетов и тесно взаимодействует с их компонентами, это создаёт tight coupling. Изменения в одном пакете могут вызвать необходимость изменений во всех зависимых пакетах.
11👍9
💬 Что такое lock-free структуры данных, и есть ли такие в Go?

Lock-free структуры данных — это тип структур данных, разработанных для многопоточных операций без использования традиционных блокировок, таких как мьютексы.

Основная идея заключается в том, чтобы обеспечить безопасность потоков и избежать проблем, связанных с блокировками, включая взаимную блокировку (deadlock) и узкие места производительности (bottlenecks).

Lock-free структуры данных обычно используют атомарные операции, такие как CAS (compare-and-swap), для обеспечения согласованности данных между потоками. Эти операции позволяют потокам соревноваться за изменение данных, но гарантируют, что только один поток сможет успешно изменить данные в любой момент времени.

В Go, языке с поддержкой конкурентности, есть несколько примеров lock-free или почти lock-free структур данных, особенно в стандартной библиотеке. Например:

1. Каналы: хотя каналы в Go не являются полностью lock-free, они предоставляют высокоуровневый способ обмена данными между горутинами без явного использования блокировок.

2. Атомарные операции: пакет sync/atomic в Go предоставляет примитивы для атомарных операций, которые являются ключевыми компонентами для создания lock-free структур данных.

3. sync.Map: предназначен для использования в кейсах, где ключи в основном не меняются, и он использует оптимизации для уменьшения необходимости блокировок.
👍23
💬 Что из себя представляют метрики рантайма в Go?

В Go, метрики рантайма относятся к данным и статистике, которые отслеживаются и собираются во время выполнения программы.

Они могут быть использованы для мониторинга и оптимизации производительности программы:

1. Память и управление памятью: Go использует сборщик мусора для автоматического освобождения памяти. Метрики, такие как количество выделенной и используемой памяти, количество сборок мусора и время, затраченное на сбор мусора, могут быть важны для оптимизации использования памяти.

2. Горутины: метрики, такие как количество активных горутин, могут помочь определить узкие места в конкурентной обработке и потенциальные проблемы с производительностью.

3. Системные вызовы и время отклика: отслеживание времени, затраченного на системные вызовы, может помочь выявить задержки во взаимодействии с операционной системой.

4. Блокировки и синхронизация: в Go важно отслеживать использование примитивов синхронизации. Метрики, относящиеся к блокировкам, могут помочь выявить проблемы с мертвыми блокировками или неэффективным использованием ресурсов.

💡Go предоставляет различные встроенные инструменты, включая pprof, для сбора и анализа этих метрик.
👍10
💬 Что такое «семплирующий профайлер»?

Семплирующий профайлер — это инструмент для профилирования производительности приложений, который работает на основе выборочного сбора данных (семплирования).

Вместо того чтобы отслеживать каждое исполнение каждой строки кода или каждый вызов функции, семплирующий профайлер периодически собирает информацию о состоянии программы в различные моменты времени. Это может включать, например, стек вызовов каждой горутины в определенные моменты времени.

В Go стандартный инструмент для семплирующего профилирования — pprof. Он позволяет собирать различные виды профилей (CPU, память, блокировки и т.д.) и предоставляет удобные средства для их анализа.