💬Какие инструменты входят в стандартную библиотеку 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
💬Есть ли в Go способ автоматического переключения контекста между горутинами?
🔻Планировщик Go автоматически переключает контекст между горутинами, когда это необходимо.
• Если вы хотите «подсказать» планировщику, что текущая горутина может быть приостановлена в пользу другой горутины, вы можете использовать функцию runtime.Gosched().
🤔Представьте, что у нас есть две горутины — одна выполняет сложные вычисления, а другая просто выводит текст. Если горутина с вычислениями использует runtime.Gosched(), это может дать горутине, которая выводит текст, шанс быстрее выполнить свою задачу.
• Готовые к исполнению горутины выполняются в порядке очереди, то есть FIFO. Исполнение горутины прерывается только тогда, когда она уже не может выполняться: то есть из-за системного вызова или использования синхронизирующих объектов. Не существует никаких квантов времени на работу горутины, после выполнения которых она бы заново возвращалась в очередь. Чтобы позволить планировщику сделать это, нужно самостоятельно вызвать runtime.Gosched().
• На практике это в первую очередь означает, что иногда стоит использовать runtime.Gosched(), чтобы несколько долгоживущих горутин не остановили на существенное время работу всех других.
🔻Планировщик 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(), чтобы несколько долгоживущих горутин не остановили на существенное время работу всех других.
👍5❤1
💬Что из себя представляет select в контексте каналов Go?
🔹Оператор select похож на switch без аргументов, но он может использоваться только для операций с каналами.
🔹select используется для выполнения операции только с одним из множества каналов, условно выбранного блоком case.
📌Основные особенности select:
• Ожидание нескольких каналов: мы можем использовать select для одновременного ожидания данных из нескольких каналов или возможности отправить данные в канал.
• Неблокирующий: если ни один из каналов не готов к операции, select может немедленно продолжить выполнение с помощью default case.
• Рандомный выбор: если несколько каналов готовы к операции одновременно, select выберет один из них случайным образом.
📌Пример использования select:
• В примере select будет ждать, пока не получит сообщение из ch1 или ch2, или пока не сможет отправить значение в ch3. Если ни одна из этих операций не может быть выполнена, будет выполнен default case.
• Таким образом, 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 для этого типа.
• Например,
2️⃣make
• make используется исключительно для создания и инициализации срезов, типа map и каналов.
• Он возвращает инициализированный (не нулевой) экземпляр указанного типа.
• Для срезов и каналов make также позволяет задавать длину и емкость.
🔸Основная разница заключается в том, что new возвращает указатель на тип с его zero value, в то время как make возвращает инициализированный тип, который готов к использованию сразу после создания.
📌Пример:
🔸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️⃣Определения функции с переменным количеством аргументов (вариативная функция). Она позволяет функции принимать неопределенное количество аргументов одного типа. Например:
2️⃣Передачи элементов среза как отдельных аргументов функции:
3️⃣Определения массивов неизвестной заранее длины:
📌Синтаксическая конструкция ‘…’ в 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 есть особенность, связанная с неиспользуемыми переменными и импортами. В чем она заключается?
🔻Особенность в том, что если в коде есть неиспользуемая локальная переменная, такой код не скомпилируется. Например:
Компилятор выдаст ошибку: x declared but not used
🔻Неиспользуемое возвращаемое значение: если функция возвращает значение, но мы его не используем, это не вызовет ошибку компиляции. Однако, если мы явно присваиваем возвращаемое значение переменной и не используем эту переменную, тогда будет ошибка.
🔻Неиспользуемый импорт: если мы импортируем пакет, но не используем его в коде:
Компилятор выдаст ошибку: "math" imported and not used.
❗️Это правило не касается неиспользуемых параметров функций и глобальных переменных. Этот код скомпилируется:
🔻Особенность в том, что если в коде есть неиспользуемая локальная переменная, такой код не скомпилируется. Например:
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!")
}
👍10❤3
💬Как с помощью стандартной библиотеки создать простой веб-сервер на Go?
📌Вот базовый пример:
1⃣Импортируем необходимые пакеты: fmt и net/http
2⃣Определяем функцию обработчика helloHandler, которая будет отвечать на все HTTP-запросы сообщением "Hello, World!"
3⃣В main регистрируем наш обработчик с помощью http.HandleFunc для корневого пути ("/")
4⃣И, наконец, запускаем веб-сервер на порту 8080 с помощью http.ListenAndServe
👉 Когда мы запустим код и перейдем по адресу http://localhost:8080, увидим сообщение "Hello, World!".
📌Либо еще проще:
📌Это что-то вроде
🚀 Кстати, недавно Eli Bendersky написал простой статический файловый сервер с поддержкой HTTP и HTTPS. Может быть полезен для локального тестирования веб-приложений.
📌Вот базовый пример:
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.Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - eliben/static-server: A simple, zero-configuration HTTP server CLI for serving static files
A simple, zero-configuration HTTP server CLI for serving static files - eliben/static-server
❤5👍4
💬Что из себя представляет тип данных rune в Go?
🔸В современном мире невозможно работать только со строками, состоящими исключительно из ASCII-символов. Везде используются нестандартные знаки, отличные от латиницы языки и эмодзи. Для работы с такими Unicode-символами в Go представлен тип данных rune.
🔸Руна в Go представляет собой тип, который является псевдонимом для int32. Он используется для представления Unicode-кода символа.
🔸Если использовать конструкцию
📌Примеры:
1. Объявление и инициализация руны:
2. Преобразование строки в срез рун:
3. Итерация по рунам в строке:
// Вывод: П р и в е т
4. Обратное преобразование среза рун в строку:
s := string(runes) // "Привет"
5. Получение Unicode-кода руны:
6. Проверка длины строки в рунах:
🔸В современном мире невозможно работать только со строками, состоящими исключительно из 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)
// 656. Проверка длины строки в рунах:
s := "Привет"
length := utf8.RuneCountInString(s)
// 6👍11❤3
💬Отличается ли обработка ошибок в Go от других ЯП? Если да, то чем?
🔺Обработка ошибок в Go существенно отличается от других ЯП и имеет некоторые ключевые особенности:
1. Явная обработка ошибок: в Go нет механизма исключений, как во многих других языках. Вместо этого функции, которые могут вызвать ошибку, обычно возвращают значение ошибки как один из своих возвращаемых результатов.
2. Множественные возвращаемые значения: функции часто возвращают результат (или результаты) и ошибку. Это позволяет легко проверять наличие ошибки после каждого вызова функции.
3. Кастомные типы ошибок: с помощью пакета
4. Добавление дополнительного контекста к ошибке: начиная с Go 1.13, были добавлены функции
5. Нет "finally": так как в Go нет исключений, нет и блока
6. panic и recover: хотя 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). Только одна горутина может захватить мьютекс и получить доступ к общему ресурсу.
🔹Здесь мы используем sync.Mutex для обеспечения безопасности при инкременте глобальной переменной count из множества горутин.
2⃣ sync.RWMutex — концептуально то же самое, что и Mutex. Тем не менее, RWMutex дает вам немного больше контроля над памятью.
🔸Он предоставляет доступ к критической секции произвольному количеству читателей и не более, чем одному писателю. При этом, если есть писатель, то читателей нет.
🔹Здесь мы используем sync.RWMutex для обеспечения безопасного доступа к кэшу. Множество горутин может одновременно читать из кэша, но только одна горутина может писать в кэш в данный момент времени.
📌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 происходит «затенение», когда переменная, объявленная во внутренней области видимости, имеет то же имя, что и переменная во внешней области видимости.
🔹В результате внутренняя переменная «затеняет» внешнюю, делая её недоступной в своей области видимости.
📌Пример:
🔹«Затенение» может быть особенно запутывающим, когда оно происходит с результатами функций, такими как err. Например, часто в Go вы можете видеть следующий код:
Если вы случайно используете := вместо =, когда присваиваете результат anotherFunction(), вы создадите новую переменную err, которая «затенит» внешнюю переменную err. Это может привести к тому, что ошибки будут проигнорированы или обработаны неправильно.
📌Как обнаружить shadowing в коде? Есть не сколько способов: использовать встроенные инструменты Go или линтеры.
1.
2.
3.
$
$
🔹В 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 выровнены по памяти. Переупорядочивание полей структуры может уменьшить ее размер.
📌Для оптимизации использования памяти в Go необходимо выполнять некоторые рекомендации, в частности:
1. Избегать глобальных переменных: глобальные переменные остаются в памяти на протяжении всего времени выполнения программы. Используйте их, когда это действительно необходимо.
2. Использовать правильные типы данных: например, вместо использования int для небольших чисел можно использовать int8/int16 и т. д., в зависимости от диапазона значений.
3. sync.Pool: если в программе часто создаются и удаляются большие объекты, мы можем использовать sync.Pool для их повторного использования.
4. Ленивая инициализация: инициализировать сложные структуры данных или большие массивы желательно только тогда, когда они действительно нужны.
5. Использовать указатели на структуры: вместо передачи копии структуры мы можем передать указатель на нее. Важно знать, что это правило работает не всегда и не везде (подробнее можно прочитать здесь).
6. Срезы vs массивы: срезы могут менять свой размер и динамически выделять память. Если размер данных известен, лучше использовать массив.
7. Освобождать ресурсы: временные большие структуры данных, которые больше не нужны, следует явно освобождать, присваивая им значение nil, чтобы сборщик мусора мог быстрее их убрать.
8. Использовать буферизацию: буферизированный ввод/вывод или буферизированные каналы могут сократить количество выделений и освобождений памяти.
9. Оптимизировать структуры: структуры в Go выровнены по памяти. Переупорядочивание полей структуры может уменьшить ее размер.
Хабр
Go: стоит ли использовать указатели вместо копий структуры?
Систематическое использование указателей для передачи структур вместо их копирования для многих разработчиков Go кажется лучшим вариантом с точки зрения производительности. Чтобы понять влияние...
👍10
💬Что из себя представляют примитивы синхронизации в контексте Go?
📌В контексте Go примитивы синхронизации — это инструменты из пакета sync (и каналы), которые помогают нам гарантировать, что множество горутин может безопасно взаимодействовать с общими данными или координировать свою работу.
🔸sync.Mutex: основной примитив блокировки для исключения одновременного доступа к данным.
🔸sync.RWMutex: разрешает множественное чтение или одну операцию записи в текущий момент времени.
🔸sync.WaitGroup: используется для ожидания завершения группы горутин.
🔸sync.Once: гарантирует, что функция будет вызвана только один раз, несмотря на количество вызовов.
🔸sync.Cond: предоставляет механизм для блокирования горутины, пока не будет выполнено некоторое условие. Не так давно Расс Кокс отменил предложение удалить данные тип в будущей версии Go.
☝️Каналы в Go хоть и не являются примитивами синхронизации в традиционном понимании, они играют ключевую роль в синхронизации и координации горутин в Go.
📌В контексте Go примитивы синхронизации — это инструменты из пакета sync (и каналы), которые помогают нам гарантировать, что множество горутин может безопасно взаимодействовать с общими данными или координировать свою работу.
🔸sync.Mutex: основной примитив блокировки для исключения одновременного доступа к данным.
🔸sync.RWMutex: разрешает множественное чтение или одну операцию записи в текущий момент времени.
🔸sync.WaitGroup: используется для ожидания завершения группы горутин.
🔸sync.Once: гарантирует, что функция будет вызвана только один раз, несмотря на количество вызовов.
🔸sync.Cond: предоставляет механизм для блокирования горутины, пока не будет выполнено некоторое условие. Не так давно Расс Кокс отменил предложение удалить данные тип в будущей версии Go.
☝️Каналы в Go хоть и не являются примитивами синхронизации в традиционном понимании, они играют ключевую роль в синхронизации и координации горутин в Go.
GitHub
proposal: sync: remove the Cond type · Issue #21165 · golang/go
In the discussion on #16620, I've noticed that the majority of use-cases folks describe for sync.Cond turn out to work fine with a channel instead: (*sync.Cond).Broadcast corresponds to calling...
👍5
💬Go — язык программирования, который отлично подходит для разработки облачных приложений. Что облачные технологии и облачные приложения из себя представляют теоретически?
📌Так звучат определения по мнению Cloud Native Computing Foundation:
• Облачные технологии позволяют создавать и запускать масштабируемые приложения в современных динамических окружениях — общедоступных, частных и гибридных облаках.
• Облачные технологии делают слабосвязанные системы устойчивыми, управляемыми и наблюдаемыми. В сочетании с надежной автоматизацией они позволяют разработчикам часто и предсказуемо вносить важные изменения с минимальными усилиями.
📝Исходя из этого, облачные приложения — больше, чем просто приложения, которые работают в облаке. Они также должны отвечать некоторым требованиям/атрибутам:
👉Масштабируемость — способность показывать ожидаемое поведение в условиях значительных колебаний спроса вверх и вниз.
👉Слабая связанность — свойство системы и стратегия проектирования, согласно которой компоненты системы знают лишь самый минимум о любых других компонентах.
👉Устойчивость — способность системы восстанавливаться после ошибок и сбоев.
👉Управляемость — простота (или ее отсутствие), с которой можно изменить поведение системы для обеспечения безопасности, бесперебойной работы и соответствия меняющимся требованиям.
👉Наблюдаемость — способность определения внутреннего состояния системы по наблюдаемым результатам.
📌Так звучат определения по мнению Cloud Native Computing Foundation:
• Облачные технологии позволяют создавать и запускать масштабируемые приложения в современных динамических окружениях — общедоступных, частных и гибридных облаках.
• Облачные технологии делают слабосвязанные системы устойчивыми, управляемыми и наблюдаемыми. В сочетании с надежной автоматизацией они позволяют разработчикам часто и предсказуемо вносить важные изменения с минимальными усилиями.
📝Исходя из этого, облачные приложения — больше, чем просто приложения, которые работают в облаке. Они также должны отвечать некоторым требованиям/атрибутам:
👉Масштабируемость — способность показывать ожидаемое поведение в условиях значительных колебаний спроса вверх и вниз.
👉Слабая связанность — свойство системы и стратегия проектирования, согласно которой компоненты системы знают лишь самый минимум о любых других компонентах.
👉Устойчивость — способность системы восстанавливаться после ошибок и сбоев.
👉Управляемость — простота (или ее отсутствие), с которой можно изменить поведение системы для обеспечения безопасности, бесперебойной работы и соответствия меняющимся требованиям.
👉Наблюдаемость — способность определения внутреннего состояния системы по наблюдаемым результатам.
👍9
💬 Что такое data race (гонка данных) в контексте Go?
📌Гонки данных — одни из наиболее распространенных и самых сложных для отладки типов ошибок в конкурентных системах. Гонка данных возникает, когда две горутины одновременно обращаются к одной и той же переменной, и хотя бы одно из обращений — запись. Чтобы избежать эту проблемы, в Go предоставляются различные примитивы синхронизации.
📌Race Condition (состояние гонки) — более широкое понятие, чем гонка данных. Оно описывает ситуацию, когда поведение программы зависит от относительного порядка выполнения операций. Гонка данных — один из видов состояний гонки, но не единственный.
Пример гонки данных, которая может привести к сбоям и повреждению памяти:
📌Гонки данных — одни из наиболее распространенных и самых сложных для отладки типов ошибок в конкурентных системах. Гонка данных возникает, когда две горутины одновременно обращаются к одной и той же переменной, и хотя бы одно из обращений — запись. Чтобы избежать эту проблемы, в Go предоставляются различные примитивы синхронизации.
📌Race Condition (состояние гонки) — более широкое понятие, чем гонка данных. Оно описывает ситуацию, когда поведение программы зависит от относительного порядка выполнения операций. Гонка данных — один из видов состояний гонки, но не единственный.
Пример гонки данных, которая может привести к сбоям и повреждению памяти:
func main() {
c := make(chan bool)
m := make(map[string]string)
go func() {
m["1"] = "a" //
Первый конфликтный доступ
c <- true
}()
m["2"] = "b" //
Второй конфликтный доступ <-c
for k, v := range m {
fmt.Println(k, v)
}
}
❗️Чтобы помочь диагностировать такие ошибки, Go включает встроенный детектор гонок данных. Для его использования добавьте флаг -race
в команду go:
$ go test -race mypkg
$ go run -race mysrc.go
$ go build -race mycmd
$ go install -race mypkg
📌Переменная окружения GORACE
устанавливает параметры детектора гонок данных, например:
$ GORACE="log_path=/tmp/race/report Strip_path_prefix=/my/go/sources/" go test -race
👉 Подробнее🔥7👍4
💬Что из себя представляет пустой идентификатор в контексте языка Go?
📌Пустой идентификатор "
👉Несколько примеров:
1. Игнорирование возвращаемых значений функции: когда функция возвращает несколько значений, но нам нужно только одно или несколько из них.
2. Игнорирование элементов в range: когда перебираются элементы среза или типа map, и нам нужен только ключ или только значение.
3. Импортирование пакета: когда нужно импортировать пакет только для того, чтобы выполнить инициализацию, определенную в нем, без использования его функций или типов.
4. Игнорирование переменных в множественном присваивании:
5. Игнорирование ошибок. Хотя так делать не рекомендуется, пустой идентификатор можно использовать для игнорирования ошибок.
6. Использование в анонимных структурах: когда создается анонимная структура, и нам не нужно давать имя одному из ее полей.
☝️Пустой идентификатор является полезным инструментом в Go и его можно безопасно использовать на практике, но злоупотреблять им точно не стоит, особенно когда речь идет об игнорировании ошибок.
📌Пустой идентификатор "
_
" в Go действует как своего рода анонимный заполнитель. Его можно использовать как любой другой идентификатор в объявлении, но с ним не будет связано никакое значение.👉Несколько примеров:
1. Игнорирование возвращаемых значений функции: когда функция возвращает несколько значений, но нам нужно только одно или несколько из них.
value, _ := someFunction()
2. Игнорирование элементов в range: когда перебираются элементы среза или типа map, и нам нужен только ключ или только значение.
for k, _ := range myMap {
fmt.Println(k)
}
for _, v := range mySlice {
fmt.Println(v)
}
3. Импортирование пакета: когда нужно импортировать пакет только для того, чтобы выполнить инициализацию, определенную в нем, без использования его функций или типов.
import _ "image/png"
4. Игнорирование переменных в множественном присваивании:
_, y := getCoordinates()
5. Игнорирование ошибок. Хотя так делать не рекомендуется, пустой идентификатор можно использовать для игнорирования ошибок.
result, _ := strconv.Atoi("123")
6. Использование в анонимных структурах: когда создается анонимная структура, и нам не нужно давать имя одному из ее полей.
person := struct {
Name string
_ int
}{"Alice", 30}
☝️Пустой идентификатор является полезным инструментом в Go и его можно безопасно использовать на практике, но злоупотреблять им точно не стоит, особенно когда речь идет об игнорировании ошибок.
👍9🔥3
💬Что из себя представляют анонимные функции и замыкания в Go?
🔸Функции в Go — обычные значения, с которыми можно работать так же, как с любыми другими объектами: они имеют типы, их можно присваивать переменным и даже передавать и возвращать другим функциям.
🔸Функции могут создаваться внутри других функций как анонимные функции, которые можно вызывать, передавать или использовать иным способом как любые другие функции. Анонимная функция — функция, которой не назначено имя.
🔸Особенностью Go является доступность состояния внешней функции из анонимных функций, даже после ее завершения. Фактически это позволяет определять замыкания. Замыкание — вложенная функция, сохраняющая доступ к переменным внешней функции даже после завершения последней.
📌Возьмем функцию incrementor. Она имеет состояние в виде переменной i и возвращает анонимную функцию, которая увеличивает значение перед возвратом. Можно сказать, что возвращаемая функция «замкнута» на переменной i.
📌Вызов incrementor создаст свою локальную копию i и вернет новую анонимную функцию, увеличивающую значение этой копии. Последующие вызовы incrementor будут создавать новые копии i:
🔸Функции в Go — обычные значения, с которыми можно работать так же, как с любыми другими объектами: они имеют типы, их можно присваивать переменным и даже передавать и возвращать другим функциям.
🔸Функции могут создаваться внутри других функций как анонимные функции, которые можно вызывать, передавать или использовать иным способом как любые другие функции. Анонимная функция — функция, которой не назначено имя.
🔸Особенностью Go является доступность состояния внешней функции из анонимных функций, даже после ее завершения. Фактически это позволяет определять замыкания. Замыкание — вложенная функция, сохраняющая доступ к переменным внешней функции даже после завершения последней.
📌Возьмем функцию incrementor. Она имеет состояние в виде переменной i и возвращает анонимную функцию, которая увеличивает значение перед возвратом. Можно сказать, что возвращаемая функция «замкнута» на переменной i.
func incrementer() func() int {
i := 0
return func() int {
i++
return i
}
}
📌Вызов incrementor создаст свою локальную копию i и вернет новую анонимную функцию, увеличивающую значение этой копии. Последующие вызовы incrementor будут создавать новые копии i:
func main() {
increment := incrementer()
fmt.Println(increment())
// 1
fmt.Println(increment())
// 2
fmt.Println(increment())
// 3
newIncrement := incrementer()
fmt.Println(newIncrement())
// 1
}
🔥16👍1
💬Как проверить тип переменной во время выполнения программы в Go?
📌Оператор switch — лучший способ проверить тип переменной во время выполнения. Каждый switch содержит по крайней мере один оператор case, который действует как условный оператор, и default case, который выполняется, если ни один из блоков case не является истинным.
👉 Например, мы можем создать swich, который проверяет, содержит ли значение интерфейса i тип int или string:
👉 Результат:
Double 21 is 42
"hello" is 5 bytes long
I don't know type bool!
📌Оператор switch — лучший способ проверить тип переменной во время выполнения. Каждый switch содержит по крайней мере один оператор case, который действует как условный оператор, и default case, который выполняется, если ни один из блоков case не является истинным.
👉 Например, мы можем создать swich, который проверяет, содержит ли значение интерфейса i тип int или string:
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Double %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
👉 Результат:
Double 21 is 42
"hello" is 5 bytes long
I don't know type bool!
👍9