Библиотека Go для собеса | вопросы с собеседований
6.88K subscribers
223 photos
7 videos
1 file
432 links
Вопросы с собеседований по Go и ответы на них.

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

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

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

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
💬Что такое inlining в Go и для чего он предназначен?

💡Inlining (встраивание) — это процесс оптимизации, при котором компилятор заменяет вызов функции её телом. Это может ускорить выполнение программы, так как уменьшает накладные расходы, связанные с вызовом функции.

📌Например:

func add(a, b int) int {
return a + b
}

func main() {
result := add(3, 4)
fmt.Println(result)
}

После встраивания код может выглядеть примерно так:

func main() {
result := 3 + 4
fmt.Println(result)
}

📌Важно:

Автоматическое принятие решения: компилятор Go автоматически решает, стоит ли делать встраивание для конкретной функции. Это решение основано на различных критериях, включая размер функции и сложность вызова.
Преимущества: основное преимущество встраивания заключается в уменьшении накладных расходов на вызов функции. Это может значительно ускорить выполнение кода, особенно если функция вызывается многократно.
Ограничения: не все функции подходят для встраивания. Очень большие функции или функции, вызывающие множество других функций, могут не быть встроены. Также слишком активное использование встраивания может увеличить размер исполняемого файла, что может привести к другим проблемам производительности.
Просмотр решений компилятора: мы можем использовать флаг -gcflags="-m" при компиляции, чтобы увидеть, какие решения принимает компилятор относительно встраивания и других оптимизаций.
👍17
💬Какие изменения в Go 1.21 коснулись работы с типом map?

🔥В Go 1.21 в стандартную библиотеку добавили пакет maps, который предоставляет несколько общих операций с типом map.

🔸func Clone(m M) M
🔸func Copy(dst M1, src M2)
🔸func DeleteFunc(m M, del func(K, V) bool)
🔸func Equal(m1 M1, m2 M2) bool
🔸func EqualFunc(m1 M1, m2 M2, eq func(V1, V2) bool) bool

📌Примеры:

👉 DeleteFunc

package main

import (
"fmt"
"maps"
)

func main() {
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
"four": 4,
}
maps.DeleteFunc(m, func(k string, v int) bool {
return v%2 != 0 // delete odd values
})
fmt.Println(m)
}


Результат: map[four:4 two:2]

👉 EqualFunc

package main

import (
"fmt"
"maps"
"strings"
)

func main() {
m1 := map[int]string{
1: "one",
10: "Ten",
1000: "THOUSAND",
}
m2 := map[int][]byte{
1: []byte("One"),
10: []byte("Ten"),
1000: []byte("Thousand"),
}
eq := maps.EqualFunc(m1, m2, func(v1 string, v2 []byte) bool {
return strings.ToLower(v1) == strings.ToLower(string(v2))
})
fmt.Println(eq)
}

Результат: true
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15👍2
💬Как в Go происходит передача параметров в функцию?

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

🔹Чтобы работать именно с той же самой переменной, не копируя ее, необходимо использовать адрес этой переменной. При этом сам указатель будет скопирован.

📌Передача базовых типов данных (int, string, bool и т.д.)

func modifyValue(x int) {
x = x * 2
}

func main() {
num := 10
modifyValue(num)
fmt.Println(num) // 10
}


Значение переменной num не изменилось после вызова функции modifyValue, так как она получила копию значения num.

📌Передача срезов и типа map

Срезы и map передаются в функции по ссылке, что означает, что функция получает ссылку на оригинальный объект данных, а не его копию. Изменения, внесенные в срез или map внутри функции, отразятся на оригинальном объекте.

func modifySlice(s []int) {
s[0] = 100
}

func main() {
numbers := []int{1, 2, 3}
modifySlice(numbers)
fmt.Println(numbers) // [100 2 3]
}


📌Передача указателей

Указатели позволяют передавать ссылку на память, в которой хранится значение. Изменения, внесенные в значение, на которое указывает указатель, будут видны за пределами функции.

func modifyWithPointer(x *int) {
*x = *x * 2
}


func main() {
num := 10
modifyWithPointer(&num)
fmt.Println(num) // 20
}


📝Таким образом, передача параметров в функции в Go зависит от типа данных параметра. Базовые типы передаются по значению (копией), а срезы, тип map и указатели передаются по ссылке, что позволяет изменять значения внутри функции и видеть эти изменения за пределами функции.
👍15
💬Что из себя представляют указатели (pointers) в Go?

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

📌Определение и инициализация
• Указатель определяется с использованием символа * перед типом. Например, var x *int определяет указатель на переменную типа int.
• Для получения адреса переменной используется оператор &. Например, если у нас есть var y int, то &y вернет указатель на y.

📌Dereferencing (разыменование)
• Чтобы получить значение, на которое ссылается указатель, мы также используем символ *. Если x — это указатель на переменную y, то *x вернет значение y.

📌Nil указатели
• Указатель может быть nil — то есть он не указывает ни на какую область памяти. Разыменование nil указателя приведет к ошибке времени выполнения.

📌Указатели и функции
• По умолчанию все параметры передаются в функцию по значению. Если нам все-таки надо менять значение передаваемой переменной, мы можем использовать указатели.

📌Указатели на структуры
• В Go часто используются указатели на структуры. Это позволяет эффективно изменять данные в структуре и избегать копирования. Когда у нас есть указатель на структуру, мы можем использовать '.' для доступа к ее полям, не разыменовывая указатель явно, например, pointerToStruct.FieldName.

📌Осторожность с указателями
• Неправильное использование указателей может привести к ошибкам, таким как разыменование nil указателя или «висячие» указатели.

📌Сравнение с другими языками
• В отличие от некоторых других языков вроде C или C++, Go упрощает работу с указателями, предоставляя автоматическое управление памятью и сборку мусора. Однако необходимо быть внимательным и понимать, как и когда их использовать.
👍15
💬Что такое GOMAXPROCS и зачем она нужна в Go?

📌GOMAXPROCS — это переменная окружения и функция в стандартной библиотеке Go, определяющая максимальное количество системных потоков, которые могут одновременно выполнять код Go.

🔹Цель: Go использует горутины, которые мультиплексируются на меньшее количество системных потоков. GOMAXPROCS определяет, сколько из этих системных потоков может быть активным (то есть выполнять код Go) одновременно.

🔹Значение по умолчанию: по умолчанию GOMAXPROCS устанавливается равным количеству ядер CPU вашей машины, что обычно является разумным выбором для большинства приложений.

🔹Изменение значения: в случае необходимости мы можем изменить значение GOMAXPROCS с помощью функции runtime.GOMAXPROCS().

💡Изменение GOMAXPROCS может повлиять на производительность вашего приложения. Например, установка GOMAXPROCS в 1 приведет к тому, что все горутины будут выполняться на одном системном потоке, что может быть полезно для отладки, но неэффективно для производительности.

🤩У команды Uber есть замечательный инструмент automaxprocs, который позволяет автоматически устанавливать GOMAXPROCS в соответствии с квотой процессора Linux-контейнера.
👍6
💬Какие инструменты входят в стандартную библиотеку Go и зачем они нужны?

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

📌Вот некоторые из ключевых инструментов:

▫️go fmt
— автоматически форматирует исходный код Go в соответствии со стандартным стилем. Это обеспечивает консистентность стиля кода во всей экосистеме Go и упрощает чтение кода.
▫️go get — используется для автоматической загрузки и установки пакетов из репозиториев. Он может клонировать репозиторий, собирать исходный код и устанавливать бинарные файлы.
▫️go test — используется для запуска тестов.
▫️go build — компилирует исходный код Go. Он создаёт исполняемый файл из исходного кода без необходимости явно указывать все файлы или зависимости.
▫️go run — используется для быстрого запуска программы Go без явной предварительной компиляции. По сути, это комбинация go build и выполнения полученного бинарного файла.
▫️go doc — позволяет получить документацию по пакетам, функциям и многому другому прямо из командной строки.
▫️go vet — используется для статического анализа исходного кода. Он ищет распространенные ошибки в коде, которые не всегда являются синтаксическими ошибками.
▫️go mod — используется для управления зависимостями. Он позволяет создавать новые модули, обновлять зависимости, и так далее.
15👍2
💬С управлением памятью в Go мы уже немного разобрались. А как можно вручную управлять сборщиком мусора?

В Go сборщик мусора (GC) автоматически управляет выделением и освобождением памяти. Однако иногда нам может потребоваться влиять на его работу или настраивать его поведение.

📌Управление сборщиком мусора

🔹Принудительный запуск GC: мы можем явно запустить сборщик мусора, вызвав функцию runtime.GC(). Однако в большинстве случаев лучше довериться автоматическому управлению сборщиком мусора.

🔹Отключение и включение GC

• Мы можем временно отключить сборщик мусора с помощью debug.SetGCPercent(-1).
• Чтобы включить его обратно, можно вызвать debug.SetGCPercent(100), где 100 — это стандартное значение.

📌Что такое GOGC?

🔹GOGC — это переменная окружения, которая контролирует частоту сборки мусора в Go. Она определяет процентное увеличение памяти, которое должно произойти между двумя последовательными сборками мусора.
• Если GOGC=100 (значение по умолчанию), это означает, что следующий GC будет запущен, когда общий объем выделенной памяти увеличится на 100% по сравнению с объемом памяти, освобожденным на последнем проходе GC.
• Если GOGC=50, сборщик мусора будет запускаться чаще: при увеличении выделенной памяти на 50%.
• Если GOGC=-1, сборщик мусора будет отключен.

🔹Значение GOGC можно установить перед запуском программы: GOGC=50 ./projname

🔹Или внутри программы с помощью debug.SetGCPercent().

🔹Важно понимать, что изменение частоты работы сборщика мусора может повлиять на производительность. Увеличение значения GOGC может уменьшить частоту сборок мусора, но при этом увеличит потребление памяти. Наоборот, уменьшение GOGC приведет к более частым сборкам мусора, что может уменьшить потребление памяти, но увеличить нагрузку на процессор.
👍7🔥71
💬Существуют ли в Go переменные окружения? Если да, какие наиболее часто используемые?

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

📌Вот некоторые из наиболее часто используемых:

• GOEXPERIMENTAL: позволяет включать или выключать экспериментальные возможности компилятора
GOARCH: архитектура целевой системы
GOBIN: место, куда будут установлены скомпилированные исполняемые файлы при использовании go install
GOEXE: расширение исполняемых файлов для текущей ОС
GOFLAGS: флаги по умолчанию для всех команд Go
• GOOS: ОС для сборки
• GOPATH: местоположение рабочего окружения
• GOPROXY: URL-адрес прокси-сервера для скачивания модулей
• GOROOT: местоположение всей бинарной сборки Go и исходных кодов
• GOSUMDB: определяет имя используемой базы данных контрольных сумм и, при необходимости, ее открытый ключ и URL-адрес
• GOTMPDIR: директория для временных файлов
• GOVCS: используется для изменения разрешенных систем контроля версий для определенных модулей
• GO111MODULE: управляет режимом работы модулей
👍11🔥4
💬Есть ли в Go способ автоматического переключения контекста между горутинами?

🔻Планировщик Go автоматически переключает контекст между горутинами, когда это необходимо.

Если вы хотите «подсказать» планировщику, что текущая горутина может быть приостановлена в пользу другой горутины, вы можете использовать функцию runtime.Gosched().

🤔Представьте, что у нас есть две горутины — одна выполняет сложные вычисления, а другая просто выводит текст. Если горутина с вычислениями использует runtime.Gosched(), это может дать горутине, которая выводит текст, шанс быстрее выполнить свою задачу.

package main

import (
"fmt"
"runtime"
"time"
)

func main() {
go func() {
for i := 0; i < 10; i++ {
fmt.Println("Вычисления:", i)
runtime.Gosched()
}
}()

go func() {
for i := 0; i < 10; i++ {
fmt.Println("Печать текста:", i)
time.Sleep(10 * time.Millisecond)
}
}()

time.Sleep(1 * time.Second)
}


Готовые к исполнению горутины выполняются в порядке очереди, то есть FIFO. Исполнение горутины прерывается только тогда, когда она уже не может выполняться: то есть из-за системного вызова или использования синхронизирующих объектов. Не существует никаких квантов времени на работу горутины, после выполнения которых она бы заново возвращалась в очередь. Чтобы позволить планировщику сделать это, нужно самостоятельно вызвать runtime.Gosched().

На практике это в первую очередь означает, что иногда стоит использовать runtime.Gosched(), чтобы несколько долгоживущих горутин не остановили на существенное время работу всех других.
👍51
💬Что из себя представляет select в контексте каналов Go?

🔹Оператор select похож на switch без аргументов, но он может использоваться только для операций с каналами.

🔹select используется для выполнения операции только с одним из множества каналов, условно выбранного блоком case.

📌Основные особенности select:

Ожидание нескольких каналов: мы можем использовать select для одновременного ожидания данных из нескольких каналов или возможности отправить данные в канал.
Неблокирующий: если ни один из каналов не готов к операции, select может немедленно продолжить выполнение с помощью default case.
Рандомный выбор: если несколько каналов готовы к операции одновременно, select выберет один из них случайным образом.

📌Пример использования select:

select {
case msg1 := <-ch1:
fmt.Println("Received", msg1)
case msg2 := <-ch2:
fmt.Println("Received", msg2)
case ch3 <- 3:
fmt.Println("Sent 3 to ch3")
default:
fmt.Println("No communication")
}


В примере select будет ждать, пока не получит сообщение из ch1 или ch2, или пока не сможет отправить значение в ch3. Если ни одна из этих операций не может быть выполнена, будет выполнен default case.

Таким образом, select предоставляет инструмент для работы с конкурентностью и каналами в Go, позволяя эффективно управлять множественными операциями ввода-вывода.
4👍4
💬Чем отличается make и new в Go? Почему бы не сравнить несравнимое…

🔸make и new — два встроенных механизма в Go для выделения памяти, но они используются в разных ситуациях и имеют разные особенности.

1️⃣new
new используется для выделения памяти для значения данного типа и возвращает указатель на этот тип.
Он инициализирует значение zero value для этого типа.
Например, new(int) вернет указатель на новое значение int, инициализированное нулем.

2️⃣make
make используется исключительно для создания и инициализации срезов, типа map и каналов.
Он возвращает инициализированный (не нулевой) экземпляр указанного типа.
Для срезов и каналов make также позволяет задавать длину и емкость.

🔸Основная разница заключается в том, что new возвращает указатель на тип с его zero value, в то время как make возвращает инициализированный тип, который готов к использованию сразу после создания.

📌Пример:

s := make([]int, 5) // создает срез длиной 5, готовый к использованию
p := new(int) // создает int со значением 0 и возвращает указатель на него
👍17🤔1
💬Для чего в Go используются синтаксическая конструкция ‘…’?

📌Синтаксическая конструкция ‘…’ в Go используется в нескольких контекстах, в частности для:

1️⃣Определения функции с переменным количеством аргументов (вариативная функция). Она позволяет функции принимать неопределенное количество аргументов одного типа. Например:

func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}


2️⃣Передачи элементов среза как отдельных аргументов функции:

nums := []int{1, 2, 3, 4}
sum(nums...)


3️⃣Определения массивов неизвестной заранее длины:

x := [...]int{1, 2, 3} // Здесь Go определит длину массива на основе количества элементов
🔥12👍4👏1
💬В Go есть особенность, связанная с неиспользуемыми переменными и импортами. В чем она заключается?

🔻Особенность в том, что если в коде есть неиспользуемая локальная переменная, такой код не скомпилируется. Например:

package main

import "fmt"

func main() {
x := 10
fmt.Println("Hello, World!")
}

Компилятор выдаст ошибку: x declared but not used

🔻Неиспользуемое возвращаемое значение: если функция возвращает значение, но мы его не используем, это не вызовет ошибку компиляции. Однако, если мы явно присваиваем возвращаемое значение переменной и не используем эту переменную, тогда будет ошибка.

package main

import "errors"

func doSomething() (int, error) {
return 42, errors.New("an error occurred")
}

func main() {
doSomething() // Это допустимо
result, err := doSomething()
// Это вызовет ошибку, так как result и err не используются
}

🔻Неиспользуемый импорт: если мы импортируем пакет, но не используем его в коде:

package main

import "math"

func main() {
// Ничего не делаем
}


Компилятор выдаст ошибку: "math" imported and not used.

❗️Это правило не касается неиспользуемых параметров функций и глобальных переменных. Этот код скомпилируется:

package main

import "fmt"

var globalVar int

func greet(name string, age int) {
fmt.Println("Hello,", name)
}

func main() {
greet("Alice", 25)
fmt.Println("Hello, World!")
}
👍103
💬Как с помощью стандартной библиотеки создать простой веб-сервер на Go?

📌Вот базовый пример:

package main

import (
"fmt"
"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}

func main() {
http.HandleFunc("/", helloHandler)
http.ListenAndServe(":8080", nil)
}


1⃣Импортируем необходимые пакеты: fmt и net/http
2⃣Определяем функцию обработчика helloHandler, которая будет отвечать на все HTTP-запросы сообщением "Hello, World!"
3⃣В main регистрируем наш обработчик с помощью http.HandleFunc для корневого пути ("/")
4⃣И, наконец, запускаем веб-сервер на порту 8080 с помощью http.ListenAndServe

👉 Когда мы запустим код и перейдем по адресу http://localhost:8080, увидим сообщение "Hello, World!".

📌Либо еще проще:

package main

import "net/http"

func main() {
port := ":8080"
handler := http.FileServer(http.Dir("."))
http.ListenAndServe(port, handler)
}


📌Это что-то вроде python -m SimpleHTTPServer <port>, только на Go.

🚀 Кстати, недавно Eli Bendersky написал простой статический файловый сервер с поддержкой HTTP и HTTPS. Может быть полезен для локального тестирования веб-приложений.
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍4
💬Что из себя представляет тип данных rune в Go?

🔸В современном мире невозможно работать только со строками, состоящими исключительно из ASCII-символов. Везде используются нестандартные знаки, отличные от латиницы языки и эмодзи. Для работы с такими Unicode-символами в Go представлен тип данных rune.

🔸Руна в Go представляет собой тип, который является псевдонимом для int32. Он используется для представления Unicode-кода символа.

🔸Если использовать конструкцию for range, строка автоматически будет преобразована в []rune, то есть обход будет по Unicode-символам.

📌Примеры:

1. Объявление и инициализация руны:

var r rune = 'A'

2. Преобразование строки в срез рун:

s := "Привет"
runes := []rune(s)


3. Итерация по рунам в строке:

for _, r := range "Привет" {
fmt.Printf("%c ", r)
}

// Вывод: П р и в е т


4. Обратное преобразование среза рун в строку:

runes := []rune{'П', 'р', 'и', 'в', 'е', 'т'}
s := string(runes) // "Привет"

5. Получение Unicode-кода руны:

r := 'A'
code := int32(r)
// 65


6. Проверка длины строки в рунах:

s := "Привет"
length := utf8.RuneCountInString(s)
// 6
👍113
💬Отличается ли обработка ошибок в Go от других ЯП? Если да, то чем?

🔺Обработка ошибок в Go существенно отличается от других ЯП и имеет некоторые ключевые особенности:

1. Явная обработка ошибок: в Go нет механизма исключений, как во многих других языках. Вместо этого функции, которые могут вызвать ошибку, обычно возвращают значение ошибки как один из своих возвращаемых результатов.

2. Множественные возвращаемые значения: функции часто возвращают результат (или результаты) и ошибку. Это позволяет легко проверять наличие ошибки после каждого вызова функции.

val, err := someFunction()
if err != nil {
// обработка ошибки
}


3. Кастомные типы ошибок: с помощью пакета errors можно создавать кастомные типы ошибок. Это дает возможность добавить дополнительную информацию к ошибке или создать проверяемые типы ошибок.

4. Добавление дополнительного контекста к ошибке: начиная с Go 1.13, были добавлены функции errors.Is, errors.As и fmt.Errorf для обертывания ошибок, что позволяет сохранить исходную ошибку и добавить дополнительный контекст.

func DoSomething() error {
if err := someOperation(); err != nil {
return fmt.Errorf("someOperation failed: %w", err)
}
return nil
}


5. Нет "finally": так как в Go нет исключений, нет и блока finally. Очистка ресурсов или другие завершающие действия обычно выполняются с использованием defer.

6. panic и recover: хотя Go предпочитает явную обработку ошибок, существуют механизмы panic и recover для обработки исключительных ситуаций. Однако их рекомендуется использовать осторожно и в основном для обработки действительно неожиданных ошибок, таких как выход за границы массива.
👍5🔥4
💬Что из себя представляют мьютексы в Go и какие типы мьютексов бывают?

📌Mutex означает mutual exclusion (взаимное исключение) и является способом защиты критической секции (critical section) программы. Мьютексы — один из наиболее распространенных примитивов синхронизации.

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

📌Стандартная библиотека Go предоставляет два типа мьютексов для синхронизации доступа к общим ресурсам:

1⃣ sync.Mutex — стандартный мьютекс, который предоставляет эксклюзивную блокировку (exclusive lock). Только одна горутина может захватить мьютекс и получить доступ к общему ресурсу.

package main

import (
"fmt"
"sync"
)

var count int
var mu sync.Mutex

func increment() {
mu.Lock()
count++
mu.Unlock()
}

func main() {
var wg sync.WaitGroup

for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
increment()
wg.Done()
}()
}

wg.Wait()
fmt.Println(count)
}


🔹Здесь мы используем sync.Mutex для обеспечения безопасности при инкременте глобальной переменной count из множества горутин.

2⃣ sync.RWMutex — концептуально то же самое, что и Mutex. Тем не менее, RWMutex дает вам немного больше контроля над памятью.

🔸Он предоставляет доступ к критической секции произвольному количеству читателей и не более, чем одному писателю. При этом, если есть писатель, то читателей нет.

package main

import (
"fmt"
"sync"
"time"
)

var cache = make(map[string]string)
var mu sync.RWMutex

func set(key string, value string) {
mu.Lock()
cache[key] = value
mu.Unlock()
}

func get(key string) string {
mu.RLock()
defer mu.RUnlock()
return cache[key]
}

func main() {
set("name", "John")

var wg sync.WaitGroup

for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
fmt.Println(get("name"))
wg.Done()
}()
}

time.Sleep(1 * time.Second)
set("name", "Doe")

wg.Wait()
}


🔹Здесь мы используем sync.RWMutex для обеспечения безопасного доступа к кэшу. Множество горутин может одновременно читать из кэша, но только одна горутина может писать в кэш в данный момент времени.
🔥7👍4
💬В Go есть такое понятие, как «затенение» (англ. shadowing) переменной. Что оно из себя представляет?

🔹В Go происходит «затенение», когда переменная, объявленная во внутренней области видимости, имеет то же имя, что и переменная во внешней области видимости.

🔹В результате внутренняя переменная «затеняет» внешнюю, делая её недоступной в своей области видимости.

📌Пример:

package main

import (

"fmt"
)

func main() {
x := 10

if true {
x := 5 // здесь происходит затенение внешней переменной x
fmt.Println(x) // выводит 5, т. к. используется внутренняя переменная x
}

fmt.Println(x) // выводит 10, т. к. используется внешняя переменная x
}

🔹«Затенение» может быть особенно запутывающим, когда оно происходит с результатами функций, такими как err. Например, часто в Go вы можете видеть следующий код:

value, err := someFunction()
if err != nil {

// обработка ошибки
}

// ...

value2, err := anotherFunction() // здесь может произойти «затенение», если использовать := вместо =
if err != nil {
// обработка ошибки
}

Если вы случайно используете := вместо =, когда присваиваете результат anotherFunction(), вы создадите новую переменную err, которая «затенит» внешнюю переменную err. Это может привести к тому, что ошибки будут проигнорированы или обработаны неправильно.

📌Как обнаружить shadowing в коде? Есть не сколько способов: использовать встроенные инструменты Go или линтеры.

1. go vet -shadow ./...
2. golangci-lint run --enable shadow
3.
$ go get -u golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
$ go vet -vettool=$(which shadow)
👍13🔥11
💬Как можно оптимизировать использование памяти в Go, особенно при работе с большими структурами данных?

📌Для оптимизации использования памяти в Go необходимо выполнять некоторые рекомендации, в частности:

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

2. Использовать правильные типы данных: например, вместо использования int для небольших чисел можно использовать int8/int16 и т. д., в зависимости от диапазона значений.

3. sync.Pool: если в программе часто создаются и удаляются большие объекты, мы можем использовать sync.Pool для их повторного использования.

4. Ленивая инициализация: инициализировать сложные структуры данных или большие массивы желательно только тогда, когда они действительно нужны.

5. Использовать указатели на структуры: вместо передачи копии структуры мы можем передать указатель на нее. Важно знать, что это правило работает не всегда и не везде (подробнее можно прочитать здесь).

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

7. Освобождать ресурсы: временные большие структуры данных, которые больше не нужны, следует явно освобождать, присваивая им значение nil, чтобы сборщик мусора мог быстрее их убрать.

8. Использовать буферизацию: буферизированный ввод/вывод или буферизированные каналы могут сократить количество выделений и освобождений памяти.

9. Оптимизировать структуры: структуры в Go выровнены по памяти. Переупорядочивание полей структуры может уменьшить ее размер.
👍10
💬Что из себя представляют примитивы синхронизации в контексте Go?

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

🔸sync.Mutex: основной примитив блокировки для исключения одновременного доступа к данным.

🔸sync.RWMutex: разрешает множественное чтение или одну операцию записи в текущий момент времени.

🔸sync.WaitGroup: используется для ожидания завершения группы горутин.

🔸sync.Once: гарантирует, что функция будет вызвана только один раз, несмотря на количество вызовов.

🔸sync.Cond: предоставляет механизм для блокирования горутины, пока не будет выполнено некоторое условие. Не так давно Расс Кокс отменил предложение удалить данные тип в будущей версии Go.

☝️Каналы в Go хоть и не являются примитивами синхронизации в традиционном понимании, они играют ключевую роль в синхронизации и координации горутин в Go.
👍5