💬Что такое embedding в Go?
📌В Go нет наследования в традиционном смысле ООП, но есть механизм, который позволяет добиться похожего эффекта — это embedding. Embedding позволяет одному типу включать в себя поля и методы другого типа без явного наследования.
◾Простота: embedding очень прост в использовании — просто определите один тип внутри другого.
◾Композиция вместо наследования: вместо того чтобы наследовать методы и поля, Go предпочитает композицию, где один тип может включать в себя другой, дополняя его функциональностью.
◾Поведение и интерфейсы: если встроенный тип реализует определенный интерфейс, то и тип, в который он встроен, автоматически реализует этот интерфейс.
📌Рассмотрим на примере:
📌Пример со встроенными методами:
👉 Теперь
📌Важно:
🔸Имена полей и конфликты: если встроенный и внешний типы имеют поля или методы с одинаковыми именами, приоритет будет у внешнего типа.
🔸Неявное поведение: одним из возможных «подводных камней» является то, что методы встроенного типа становятся частью внешнего типа, что может быть не всегда очевидным при чтении кода.
🔸Интерфейсы и встраивание: в Go можно также встраивать интерфейсы, что позволяет создавать сложные интерфейсы на основе уже существующих.
📌В Go нет наследования в традиционном смысле ООП, но есть механизм, который позволяет добиться похожего эффекта — это embedding. Embedding позволяет одному типу включать в себя поля и методы другого типа без явного наследования.
◾Простота: embedding очень прост в использовании — просто определите один тип внутри другого.
◾Композиция вместо наследования: вместо того чтобы наследовать методы и поля, Go предпочитает композицию, где один тип может включать в себя другой, дополняя его функциональностью.
◾Поведение и интерфейсы: если встроенный тип реализует определенный интерфейс, то и тип, в который он встроен, автоматически реализует этот интерфейс.
📌Рассмотрим на примере:
type Engine struct {
Power int
Type string
}
type Car struct {
Engine
Brand string
Model string
}
func main() {
c := Car{
Engine: Engine{Power: 150, Type: "Petrol"},
Brand: "Ford",
Model: "Fiesta",
}
fmt.Println(c.Power)
}
📌Пример со встроенными методами:
type Writer interface {
Write([]byte) (int, error)
}
type Logger struct {
Writer
}
👉 Теперь
Logger
автоматически реализует интерфейс Writer
, но только в том случае, если его встроенное поле Writer
также реализует методы этого интерфейса. 📌Важно:
🔸Имена полей и конфликты: если встроенный и внешний типы имеют поля или методы с одинаковыми именами, приоритет будет у внешнего типа.
🔸Неявное поведение: одним из возможных «подводных камней» является то, что методы встроенного типа становятся частью внешнего типа, что может быть не всегда очевидным при чтении кода.
🔸Интерфейсы и встраивание: в Go можно также встраивать интерфейсы, что позволяет создавать сложные интерфейсы на основе уже существующих.
👍8❤1
💬Какие изменения в Go 1.21 коснулись срезов?
🔥 В Go 1.21 в стандартную библиотеку добавили пакет slices, который значительно облегчил работу со срезами. Вот только некоторые новые функции:
🔸Поиск элемента в срезе:
🔸Сортировка (для любых ordered типов):
🔸Поиск максимума:
🔸Удаление элемента:
🔸Сравнение двух срезов:
🔸Compact — заменяет последовательные серии одинаковых элементов одной копией — как команда uniq в *nix.
🔸Equal — проверяет, равны ли два среза: одинаковая длина и все элементы равны.
🔸Clip — удаляет неиспользуемую емкость из среза, возвращая
🔸Clone — возвращает копию среза.
🔸BinarySearch — ищет элемент в отсортированном срезе и возвращает позицию, где элемент найден, или позицию, где он появится в порядке сортировки.
Подробнее:
🔗 Release notes & pkg.go.dev & Ardan Labs
🔸Поиск элемента в срезе:
slices.Contains(s, v)
🔸Сортировка (для любых ordered типов):
slices.Sort(s)
🔸Поиск максимума:
maxVal := slices.Max(s)
🔸Удаление элемента:
letters := []string{"a", "b", "c", "d", "e"}
letters = slices.Delete(letters, 1, 4)
🔸Сравнение двух срезов:
areEqual := slices.Compare(a, b)
🔸Compact — заменяет последовательные серии одинаковых элементов одной копией — как команда uniq в *nix.
seq := []int{0, 1, 1, 2, 3, 5, 8}
seq = slices.Compact(seq)
🔸Equal — проверяет, равны ли два среза: одинаковая длина и все элементы равны.
numbers := []int{0, 42, 8}
fmt.Println(slices.Equal(numbers, []int{0, 42, 8}))
// true🔸Clip — удаляет неиспользуемую емкость из среза, возвращая
s[:len(s):len(s)]
.list = slices.Clip(list)
🔸Clone — возвращает копию среза.
🔸BinarySearch — ищет элемент в отсортированном срезе и возвращает позицию, где элемент найден, или позицию, где он появится в порядке сортировки.
names := []string{"Alice", "Bob", "Vera"}
n, found := slices.BinarySearch(names, "Vera")
Подробнее:
🔗 Release notes & pkg.go.dev & Ardan Labs
Please open Telegram to view this post
VIEW IN TELEGRAM
tip.golang.org
Go 1.21 Release Notes - The Go Programming Language
👍32
💬Какие основные отличия горутины от thread (потока) в Go
1⃣Уровень абстракции:
• Горутины — это абстракции уровня языка, предоставляемые Go. Они позволяют выполнять функции или методы конкурентно.
• Потоки — это более традиционные сущности операционной системы для параллельного выполнения задач.
2⃣Размер стека:
• Горутины начинаются с очень маленького стека, который может динамически расти и сокращаться в зависимости от потребности (обычно начинается с 2KB).
• Потоки, в зависимости от ОС, обычно имеют стек фиксированного размера, который может быть значительно больше (обычно от 1MB и выше).
3⃣Создание и переключение:
• Горутины легко создать (просто используя ключевое слово go перед вызовом функции), и они дешевы в плане создания и переключения контекста.
• Потоки дороже по стоимости создания и контекстного переключения, так как это требует прямого взаимодействия с операционной системой.
4⃣Планировщик:
• Горутины управляются планировщиком Go, который работает в пользовательском пространстве (user space) и распределяет горутины по доступным ОС потокам (обычно один поток на ядро CPU).
• Потоки управляются планировщиком ОС.
5⃣Изоляция:
• Ошибка в одной горутине (например, паника) может повлиять на все другие горутины в той же программе.
👀 Выше представлен пример создания горутины и потока в Go. Мы создаем горутину с помощью ключевого слова. Затем, меняя GOMAXPROCS, мы фактически заставляем Go использовать дополнительный поток ОС, что делает выполнение кода более похожим на многопоточное.
1⃣Уровень абстракции:
• Горутины — это абстракции уровня языка, предоставляемые Go. Они позволяют выполнять функции или методы конкурентно.
• Потоки — это более традиционные сущности операционной системы для параллельного выполнения задач.
2⃣Размер стека:
• Горутины начинаются с очень маленького стека, который может динамически расти и сокращаться в зависимости от потребности (обычно начинается с 2KB).
• Потоки, в зависимости от ОС, обычно имеют стек фиксированного размера, который может быть значительно больше (обычно от 1MB и выше).
3⃣Создание и переключение:
• Горутины легко создать (просто используя ключевое слово go перед вызовом функции), и они дешевы в плане создания и переключения контекста.
• Потоки дороже по стоимости создания и контекстного переключения, так как это требует прямого взаимодействия с операционной системой.
4⃣Планировщик:
• Горутины управляются планировщиком Go, который работает в пользовательском пространстве (user space) и распределяет горутины по доступным ОС потокам (обычно один поток на ядро CPU).
• Потоки управляются планировщиком ОС.
5⃣Изоляция:
• Ошибка в одной горутине (например, паника) может повлиять на все другие горутины в той же программе.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍6🔥1
💬Что из себя представляет паттерн «Одиночка» (Singleton) и как его реализовать на Go
Одиночка — это порождающий паттерн, который гарантирует существование только одного объекта определённого класса/типа и предоставляет к нему глобальную точку доступа.
В первом примере для гарантии создания одного экземпляра структуры используются мьютексы (sync.Mutex). Мьютекс блокирует выполнение кода так, чтобы только одна горутина могла его выполнять в данный момент времени.
📌Важно:
• В начале нужна nil-проверка, с ее помощью мы убеждаемся, что первый экземпляр — пустой. Благодаря этому мы можем избежать ресурсоемких операций блокировки при каждом вызове
• Структура
• После блокировки используется еще одна
📌Существуют и другие методы создания экземпляра одиночки в Go.
1⃣Функция init: Go предоставляет функцию init, которая вызывается автоматически при инициализации пакета. Мы можем использовать эту функцию для создания экземпляра одиночки.
2⃣sync.Once: Этот метод гарантирует, что функция будет выполнена только один раз, даже если она будет вызвана из нескольких горутин.
Одиночка — это порождающий паттерн, который гарантирует существование только одного объекта определённого класса/типа и предоставляет к нему глобальную точку доступа.
В первом примере для гарантии создания одного экземпляра структуры используются мьютексы (sync.Mutex). Мьютекс блокирует выполнение кода так, чтобы только одна горутина могла его выполнять в данный момент времени.
📌Важно:
• В начале нужна nil-проверка, с ее помощью мы убеждаемся, что первый экземпляр — пустой. Благодаря этому мы можем избежать ресурсоемких операций блокировки при каждом вызове
obtainUnique
. Если эта проверка не пройдена, тогда поле uniqueItem
уже заполнено.• Структура
uniqueItem
создается внутри блокировки.• После блокировки используется еще одна
nil
-проверка. В случаях, когда первую проверку проходит более одного потока, вторая обеспечивает создание экземпляра одиночки единым потоком. В противном случае, все потоки создавали бы свои экземпляры структуры одиночки.📌Существуют и другие методы создания экземпляра одиночки в Go.
1⃣Функция init: Go предоставляет функцию init, которая вызывается автоматически при инициализации пакета. Мы можем использовать эту функцию для создания экземпляра одиночки.
2⃣sync.Once: Этот метод гарантирует, что функция будет выполнена только один раз, даже если она будет вызвана из нескольких горутин.
👍12🔥5
💬Что такое контекст (context) в Go и для чего он применяется?
Context в Go — это специальный пакет, предназначенный для передачи параметров между API и управления жизненным циклом горутин.
Основное его назначение — передача метаданных, установка временных рамок выполнения и отслеживание отмены долгосрочных операций.
📌Основные моменты:
🔹Context введен в Go 1.7 и с тех пор является предпочтительным механизмом для управления временем выполнения и отменами.
🔹Context Interface: интерфейс
🔹Основные методы: WithCancel, WithDeadline, WithTimeout и WithValue.
• WithCancel — возвращает копию переданного контекста и cancelFunc. Вызов cancelFunc отменяет этот контекст.
• WithDeadline & WithTimeout — позволяют задать временные рамки контексту.
• WithValue — позволяет передать произвольные пары ключ/значение в context.
🔹Отмена родительского context автоматически отменяет все дочерние.
🔹Context используется для уведомления о том, что пора завершать работу, — это особенно удобно через канал
🔹Возвращаемая функция
🔹Не храните в context чувствительные данные: контекст может быть выведен и сохранен в логах, что может раскрыть чувствительные данные.
🔹При отмене context можно узнать причину через
📌Пример:
📌Важно:
•
• Когда context отменяется (через cancel, timeout или deadline),
• Context следует передавать первым аргументом в функцию:
Context в Go — это специальный пакет, предназначенный для передачи параметров между API и управления жизненным циклом горутин.
Основное его назначение — передача метаданных, установка временных рамок выполнения и отслеживание отмены долгосрочных операций.
📌Основные моменты:
🔹Context введен в Go 1.7 и с тех пор является предпочтительным механизмом для управления временем выполнения и отменами.
🔹Context Interface: интерфейс
context.Context
является основным типом, который вы передаете между функциями.🔹Основные методы: WithCancel, WithDeadline, WithTimeout и WithValue.
• WithCancel — возвращает копию переданного контекста и cancelFunc. Вызов cancelFunc отменяет этот контекст.
• WithDeadline & WithTimeout — позволяют задать временные рамки контексту.
• WithValue — позволяет передать произвольные пары ключ/значение в context.
🔹Отмена родительского context автоматически отменяет все дочерние.
🔹Context используется для уведомления о том, что пора завершать работу, — это особенно удобно через канал
ctx.Done()
.🔹Возвращаемая функция
cancel
позволяет рано завершить context.🔹Не храните в context чувствительные данные: контекст может быть выведен и сохранен в логах, что может раскрыть чувствительные данные.
🔹При отмене context можно узнать причину через
ctx.Err()
, где возможные значения — context.Canceled
или context.DeadlineExceeded
.📌Пример:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // освобождаем ресурсы
go someOperation(ctx)
if ctx.Err() == context.Canceled {
fmt.Println("Operation was canceled")
}
📌Важно:
•
context.Background()
и context.TODO()
— одно и то же. Разница лишь в том, что context.TODO()
выставляется в местах, где пока нет понимания, что необходимо использовать context.Background()
и возможно его надо заменить на дочерний context.• Когда context отменяется (через cancel, timeout или deadline),
ctx.Done()
возвращает закрытый канал. Это удобный механизм для оповещения горутин о том, что пора завершать работу.• Context следует передавать первым аргументом в функцию:
func DoSomething(ctx context.Context, arg Arg) error
👍16🔥5
💬Что из себя представляет паттерн «Функциональные опции» в Go и как его реализовать?
▪️Паттерн «Функциональные опции» в Go предлагает гибкий способ настройки структур без раскрытия внутренних полей.
▪️Он особенно полезен, если вы хотите предоставить дополнительную конфигурацию, или в будущем могут появиться дополнительные параметры конфигурации. Он также отлично подходит для библиотек.
📌Базовый пример без функциональных опций:
▪️Со временем наши требования меняются, и нам может понадобиться поддерживать больше вариантов конфигурации. Вместо изменения сигнатуры функции
👉 Сначала мы определяем функциональную опцию:
👉 и функцию, удовлетворяющую типу:
👉 изменяем нашу функцию NewServer:
👉 и теперь мы можем делать вот так:
📌Этот паттерн позволяет нам гибко настраивать параметры, сохраняя при этом читабельность и не раскрывая внутренние поля.
▪️Паттерн «Функциональные опции» в 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",
}
}
▪️Со временем наши требования меняются, и нам может понадобиться поддерживать больше вариантов конфигурации. Вместо изменения сигнатуры функции
NewServer
, что может быть проблематично и несовместимо с предыдущими версиями, мы можем использовать функциональные опции.👉 Сначала мы определяем функциональную опцию:
type ServerOption func(*Server)
👉 и функцию, удовлетворяющую типу:
func WithPort (port int) ServerOption {
return func(s *Server) {
s.port = port
}
👉 изменяем нашу функцию 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))
📌Этот паттерн позволяет нам гибко настраивать параметры, сохраняя при этом читабельность и не раскрывая внутренние поля.
👍37
💬Для чего предназначен и как работает планировщик Go (Go Scheduler) на базовом уровне?
📌Планировщик Go отвечает за управление горутинами и переключение контекста между ними, обеспечивая эффективное использование ресурсов процессора.
🔸M:N планирование: Go использует модель M:N для планирования, где M горутин могут быть запланированы на N потоках ОС. Это позволяет Go создавать тысячи горутин, минимизируя накладные расходы, связанные с созданием и управлением реальными потоками ОС.
🔸G, M и P:
• G (goroutine) — горутина
• M (machine thread) — поток ОС, на котором выполняются горутины
• P (processor) — контекст выполнения, который содержит очередь горутин и другую необходимую информацию. Число P обычно равно числу ядер процессора
🔸Работа планировщика: когда горутина начинает выполнение, она захватывает P и привязывается к M. Если горутина блокируется (например, ожидает ввода/вывода), то P может быть отсоединен и присоединен к другой горутине, которая готова к выполнению.
🔸Рост и уменьшение: планировщик динамически увеличивает или уменьшает количество потоков ОС (M), в зависимости от нагрузки и блокировок.
🔸Взаимодействие с планировщиком: горутины взаимодействуют с планировщиком Go. Они могут быть прерваны, чтобы освободить поток ОС для других горутин. Этот механизм прерывания позволяет гарантировать, что одна горутина не занимает поток ОС слишком долго, предоставляя возможность другим горутинам получить время на выполнение.
📌Планировщик Go отвечает за управление горутинами и переключение контекста между ними, обеспечивая эффективное использование ресурсов процессора.
🔸M:N планирование: Go использует модель M:N для планирования, где M горутин могут быть запланированы на N потоках ОС. Это позволяет Go создавать тысячи горутин, минимизируя накладные расходы, связанные с созданием и управлением реальными потоками ОС.
🔸G, M и P:
• G (goroutine) — горутина
• M (machine thread) — поток ОС, на котором выполняются горутины
• P (processor) — контекст выполнения, который содержит очередь горутин и другую необходимую информацию. Число P обычно равно числу ядер процессора
🔸Работа планировщика: когда горутина начинает выполнение, она захватывает P и привязывается к M. Если горутина блокируется (например, ожидает ввода/вывода), то P может быть отсоединен и присоединен к другой горутине, которая готова к выполнению.
🔸Рост и уменьшение: планировщик динамически увеличивает или уменьшает количество потоков ОС (M), в зависимости от нагрузки и блокировок.
🔸Взаимодействие с планировщиком: горутины взаимодействуют с планировщиком Go. Они могут быть прерваны, чтобы освободить поток ОС для других горутин. Этот механизм прерывания позволяет гарантировать, что одна горутина не занимает поток ОС слишком долго, предоставляя возможность другим горутинам получить время на выполнение.
👍19
💬Что из себя представляют дженерики в Go и для чего они нужны?
▪️Поддержка дженериков была добавлена в Go 1.18. Это своего рода механизм, который предоставляет инструмент для создания функций и структур данных, способных взаимодействовать с множеством типов, не требуя их явного указания при объявлении.
▪️Преимуществом использования дженериков является то, что они позволяют уменьшить дублирование кода, предоставляя универсальные решения для общих задач, и улучшают производительность, избегая необходимости в преобразованиях типов.
▪️До введения дженериков разработчики использовали интерфейсы и преобразование типов для достижения подобной гибкости, что могло приводить к потере производительности и безопасности типов.
▪️Дженерики позволяют писать более чистый, эффективный и безопасный код, минимизируя повторение кода и предоставляя строгую проверку типов. Они делают Go более универсальным и конкурентоспособным по сравнению с другими языками программирования, в которых дженерики давно были стандартом.
📌А теперь немного кода:
👉 Без дженериков
• Раньше, если вы хотели создать функцию для поиска минимума в срезе, вам пришлось бы создавать отдельные функции для каждого типа или использовать пустой интерфейс
👉 С дженериками
• С помощью дженериков, мы можем создать одну функцию, которая работает с различными типами:
• Здесь
🔸Обобщенные структуры данных
• Допустим, вы хотите создать структуру данных типа очередь (Queue).
👉 Без дженериков
• Использование
👉 С дженериками
• Здесь T может быть любым типом, и нет необходимости в преобразовании типов.
🤔В общем и целом, большинство разработчиков приветствует введение дженериков в Go, но при этом подчеркивают важность их правильного и обдуманного применения.
▪️Поддержка дженериков была добавлена в Go 1.18. Это своего рода механизм, который предоставляет инструмент для создания функций и структур данных, способных взаимодействовать с множеством типов, не требуя их явного указания при объявлении.
▪️Преимуществом использования дженериков является то, что они позволяют уменьшить дублирование кода, предоставляя универсальные решения для общих задач, и улучшают производительность, избегая необходимости в преобразованиях типов.
▪️До введения дженериков разработчики использовали интерфейсы и преобразование типов для достижения подобной гибкости, что могло приводить к потере производительности и безопасности типов.
▪️Дженерики позволяют писать более чистый, эффективный и безопасный код, минимизируя повторение кода и предоставляя строгую проверку типов. Они делают Go более универсальным и конкурентоспособным по сравнению с другими языками программирования, в которых дженерики давно были стандартом.
📌А теперь немного кода:
👉 Без дженериков
• Раньше, если вы хотели создать функцию для поиска минимума в срезе, вам пришлось бы создавать отдельные функции для каждого типа или использовать пустой интерфейс
interface{}
.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, но при этом подчеркивают важность их правильного и обдуманного применения.
👍13🔥1
💬Что такое и для чего предназначен Escape analysis в Go?
▫️Escape analysis — это техника компилятора Go, которая позволяет определить, где лучше всего размещать переменные: на стеке или в куче. Этот анализ важен для оптимизации производительности и эффективного использования памяти.
▫️В Go нет ни одного ключевого слова или функции, которую вы могли бы использовать, чтобы указать компилятору какое решение ему принять. Только то, как вы пишете свой код, условно позволяет повлиять на это решение.
📌Как это работает?
▫️Компилятор анализирует область видимости и жизненный цикл переменных в программе. Если переменная не выходит за пределы своей области видимости, то она может быть безопасно размещена на стеке. Это быстрее и эффективнее с точки зрения памяти, так как стек очищается автоматически при выходе из функции.
▫️Если же она выходит — например, возвращается из функции или сохраняется в глобальной переменной — то она будет размещена в куче, и за её освобождение будет отвечать сборщик мусора.
🚀Теперь немного кода
• Переменная остается в рамках функции:
• Переменная не ограничена локальной областью видимости и может быть доступна извне:
• Переменная сохраняется в глобальной переменной:
Эти примеры упрощены, но они иллюстрируют базовую концепцию. Escape analysis помогает языку Go работать эффективно, оптимизируя использование памяти и улучшая производительность.
▫️Escape analysis — это техника компилятора Go, которая позволяет определить, где лучше всего размещать переменные: на стеке или в куче. Этот анализ важен для оптимизации производительности и эффективного использования памяти.
▫️В Go нет ни одного ключевого слова или функции, которую вы могли бы использовать, чтобы указать компилятору какое решение ему принять. Только то, как вы пишете свой код, условно позволяет повлиять на это решение.
📌Как это работает?
▫️Компилятор анализирует область видимости и жизненный цикл переменных в программе. Если переменная не выходит за пределы своей области видимости, то она может быть безопасно размещена на стеке. Это быстрее и эффективнее с точки зрения памяти, так как стек очищается автоматически при выходе из функции.
▫️Если же она выходит — например, возвращается из функции или сохраняется в глобальной переменной — то она будет размещена в куче, и за её освобождение будет отвечать сборщик мусора.
🚀Теперь немного кода
• Переменная остается в рамках функции:
func sum(a, b int) int {
result := a + b
return result
}
• Переменная не ограничена локальной областью видимости и может быть доступна извне:
func newInt() *int {
result := 42
return &result
}
• Переменная сохраняется в глобальной переменной:
var globalVar *int
func setGlobalVar() {
x := 100
globalVar = &x
}
Эти примеры упрощены, но они иллюстрируют базовую концепцию. Escape analysis помогает языку Go работать эффективно, оптимизируя использование памяти и улучшая производительность.
👍22
💬Что из себя представляет паттерн «Прототип» в Go и как его реализовать?
🔻Паттерн Прототип позволяет создавать новые объекты, путем копирования (клонирования) созданного ранее объекта-оригинала-продукта (прототипа).
🔻Паттерн описывает процесс создания объектов-клонов на основе имеющегося объекта-прототипа, другими словами, он позволяет описать способ организации процесса клонирования.
📌А теперь практика (код прикреплен к посту)
• Рассмотрим паттерн Прототип на примере иерархии файловой системы. В такой системе есть директории, которые могут содержать в себе как файлы, так и другие директории, создавая многоуровневую структуру.
• Каждый файл или директория может быть представлен интерфейсом Element. Этот интерфейс имеет метод clone.
Файл и директория (назовем их Document и Directory) реализуют методы display и clone, соответствующие интерфейсу Element. При клонировании мы добавляем суффикс “
• В примере выше мы видим, как изначальная иерархия файловой системы успешно клонировалась, причем каждый элемент в клонированной версии имеет суффикс "
Результат выполнения:
🔻Паттерн Прототип позволяет создавать новые объекты, путем копирования (клонирования) созданного ранее объекта-оригинала-продукта (прототипа).
🔻Паттерн описывает процесс создания объектов-клонов на основе имеющегося объекта-прототипа, другими словами, он позволяет описать способ организации процесса клонирования.
📌А теперь практика (код прикреплен к посту)
• Рассмотрим паттерн Прототип на примере иерархии файловой системы. В такой системе есть директории, которые могут содержать в себе как файлы, так и другие директории, создавая многоуровневую структуру.
• Каждый файл или директория может быть представлен интерфейсом Element. Этот интерфейс имеет метод clone.
Файл и директория (назовем их Document и Directory) реализуют методы display и clone, соответствующие интерфейсу Element. При клонировании мы добавляем суффикс “
_copy
” к имени элемента.• В примере выше мы видим, как изначальная иерархия файловой системы успешно клонировалась, причем каждый элемент в клонированной версии имеет суффикс "
_copy
".Результат выполнения:
Displaying structure for
Dir2
Dir2
Dir1
Doc1
Doc2
Doc3
Displaying structure for copied Directory
Dir2_copy
Dir1_copy
Doc1_copy
Doc2_copy
Doc3_copy
👍6
💬Для чего в Go используется ключевое слово fallthrough
🔹В Go ключевое слово fallthrough используется внутри оператора switch. Оно позволяет продолжить выполнение кода в следующем блоке case, даже если его условие не выполняется.
🔹Это поведение аналогично тому, как switch работает в некоторых других языках, таких как C или C++, но в Go вы должны явно использовать fallthrough.
👀 Пример
• В этом примере, когда x равно 3, программа выведет:
• Хотя x не равно 4, из-за использования fallthrough в case 3, выполнение продолжается с case 4.
🔹В Go ключевое слово fallthrough используется внутри оператора switch. Оно позволяет продолжить выполнение кода в следующем блоке case, даже если его условие не выполняется.
🔹Это поведение аналогично тому, как switch работает в некоторых других языках, таких как C или C++, но в Go вы должны явно использовать fallthrough.
package main
import "fmt"
func main() {
x := 3
switch x {
case 3:
fmt.Println("x is 3")
fallthrough
case 4:
fmt.Println("x is 4")
default:
fmt.Println("default case")
}
}
• В этом примере, когда x равно 3, программа выведет:
x is 3
x is 4
• Хотя x не равно 4, из-за использования fallthrough в case 3, выполнение продолжается с case 4.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
💬Что такое inlining в Go и для чего он предназначен?
💡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
Результат:
👉 EqualFunc
Результат:
🔸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 и т.д.)
Значение переменной num не изменилось после вызова функции modifyValue, так как она получила копию значения num.
📌Передача срезов и типа map
Срезы и map передаются в функции по ссылке, что означает, что функция получает ссылку на оригинальный объект данных, а не его копию. Изменения, внесенные в срез или map внутри функции, отразятся на оригинальном объекте.
📌Передача указателей
Указатели позволяют передавать ссылку на память, в которой хранится значение. Изменения, внесенные в значение, на которое указывает указатель, будут видны за пределами функции.
📝Таким образом, передача параметров в функции в Go зависит от типа данных параметра. Базовые типы передаются по значению (копией), а срезы, тип map и указатели передаются по ссылке, что позволяет изменять значения внутри функции и видеть эти изменения за пределами функции.
🔹Параметры в 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 указатели используются для различных целей, включая оптимизацию производительности и управление данными на более низком уровне.
📌Определение и инициализация
• Указатель определяется с использованием символа * перед типом. Например,
• Для получения адреса переменной используется оператор &. Например, если у нас есть
📌Dereferencing (разыменование)
• Чтобы получить значение, на которое ссылается указатель, мы также используем символ *. Если x — это указатель на переменную y, то *x вернет значение y.
📌Nil указатели
• Указатель может быть nil — то есть он не указывает ни на какую область памяти. Разыменование nil указателя приведет к ошибке времени выполнения.
📌Указатели и функции
• По умолчанию все параметры передаются в функцию по значению. Если нам все-таки надо менять значение передаваемой переменной, мы можем использовать указатели.
📌Указатели на структуры
• В Go часто используются указатели на структуры. Это позволяет эффективно изменять данные в структуре и избегать копирования. Когда у нас есть указатель на структуру, мы можем использовать '.' для доступа к ее полям, не разыменовывая указатель явно, например,
📌Осторожность с указателями
• Неправильное использование указателей может привести к ошибкам, таким как разыменование nil указателя или «висячие» указатели.
📌Сравнение с другими языками
• В отличие от некоторых других языков вроде C или C++, 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-контейнера.
📌GOMAXPROCS — это переменная окружения и функция в стандартной библиотеке Go, определяющая максимальное количество системных потоков, которые могут одновременно выполнять код Go.
🔹Цель: Go использует горутины, которые мультиплексируются на меньшее количество системных потоков. GOMAXPROCS определяет, сколько из этих системных потоков может быть активным (то есть выполнять код Go) одновременно.
🔹Значение по умолчанию: по умолчанию GOMAXPROCS устанавливается равным количеству ядер CPU вашей машины, что обычно является разумным выбором для большинства приложений.
🔹Изменение значения: в случае необходимости мы можем изменить значение GOMAXPROCS с помощью функции runtime.GOMAXPROCS().
💡Изменение GOMAXPROCS может повлиять на производительность вашего приложения. Например, установка GOMAXPROCS в 1 приведет к тому, что все горутины будут выполняться на одном системном потоке, что может быть полезно для отладки, но неэффективно для производительности.
🤩У команды Uber есть замечательный инструмент automaxprocs, который позволяет автоматически устанавливать GOMAXPROCS в соответствии с квотой процессора Linux-контейнера.
GitHub
GitHub - uber-go/automaxprocs: Automatically set GOMAXPROCS to match Linux container CPU quota.
Automatically set GOMAXPROCS to match Linux container CPU quota. - uber-go/automaxprocs
👍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 — используется для управления зависимостями. Он позволяет создавать новые модули, обновлять зависимости, и так далее.
🔸В стандартную библиотеку 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: мы можем явно запустить сборщик мусора, вызвав функцию
🔹Отключение и включение GC
• Мы можем временно отключить сборщик мусора с помощью
• Чтобы включить его обратно, можно вызвать
📌Что такое GOGC?
🔹GOGC — это переменная окружения, которая контролирует частоту сборки мусора в Go. Она определяет процентное увеличение памяти, которое должно произойти между двумя последовательными сборками мусора.
• Если GOGC=100 (значение по умолчанию), это означает, что следующий GC будет запущен, когда общий объем выделенной памяти увеличится на 100% по сравнению с объемом памяти, освобожденным на последнем проходе GC.
• Если GOGC=50, сборщик мусора будет запускаться чаще: при увеличении выделенной памяти на 50%.
• Если GOGC=-1, сборщик мусора будет отключен.
🔹Значение GOGC можно установить перед запуском программы:
🔹Или внутри программы с помощью
🔹Важно понимать, что изменение частоты работы сборщика мусора может повлиять на производительность. Увеличение значения GOGC может уменьшить частоту сборок мусора, но при этом увеличит потребление памяти. Наоборот, уменьшение GOGC приведет к более частым сборкам мусора, что может уменьшить потребление памяти, но увеличить нагрузку на процессор.
В 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 приведет к более частым сборкам мусора, что может уменьшить потребление памяти, но увеличить нагрузку на процессор.
Telegram
Библиотека Go для собеса | вопросы с собеседований
💬Как работает управление памятью в Go?
📌Go использует сборщик мусора для автоматического управления памятью. Разработчику не нужно явно выделять и освобождать память, как в языках типа C или C++. Однако нужно быть внимательным при работе с большими структурами…
📌Go использует сборщик мусора для автоматического управления памятью. Разработчику не нужно явно выделять и освобождать память, как в языках типа C или C++. Однако нужно быть внимательным при работе с большими структурами…
👍7🔥7❤1
💬Существуют ли в Go переменные окружения? Если да, какие наиболее часто используемые?
🔹В Go существует несколько переменных окружения, которые влияют на поведение инструментов компиляции, сборки и других аспектов языка.
📌Вот некоторые из наиболее часто используемых:
• GOEXPERIMENTAL: позволяет включать или выключать экспериментальные возможности компилятора
• GOARCH: архитектура целевой системы
• GOBIN: место, куда будут установлены скомпилированные исполняемые файлы при использовании
• GOEXE: расширение исполняемых файлов для текущей ОС
• GOFLAGS: флаги по умолчанию для всех команд Go
• GOOS: ОС для сборки
• GOPATH: местоположение рабочего окружения
• GOPROXY: URL-адрес прокси-сервера для скачивания модулей
• GOROOT: местоположение всей бинарной сборки Go и исходных кодов
• GOSUMDB: определяет имя используемой базы данных контрольных сумм и, при необходимости, ее открытый ключ и URL-адрес
• GOTMPDIR: директория для временных файлов
• GOVCS: используется для изменения разрешенных систем контроля версий для определенных модулей
• GO111MODULE: управляет режимом работы модулей
🔹В Go существует несколько переменных окружения, которые влияют на поведение инструментов компиляции, сборки и других аспектов языка.
📌Вот некоторые из наиболее часто используемых:
• GOEXPERIMENTAL: позволяет включать или выключать экспериментальные возможности компилятора
• GOARCH: архитектура целевой системы
• GOBIN: место, куда будут установлены скомпилированные исполняемые файлы при использовании
go install
• GOEXE: расширение исполняемых файлов для текущей ОС
• GOFLAGS: флаги по умолчанию для всех команд Go
• GOOS: ОС для сборки
• GOPATH: местоположение рабочего окружения
• GOPROXY: URL-адрес прокси-сервера для скачивания модулей
• GOROOT: местоположение всей бинарной сборки Go и исходных кодов
• GOSUMDB: определяет имя используемой базы данных контрольных сумм и, при необходимости, ее открытый ключ и URL-адрес
• GOTMPDIR: директория для временных файлов
• GOVCS: используется для изменения разрешенных систем контроля версий для определенных модулей
• GO111MODULE: управляет режимом работы модулей
👍11🔥4