🤔 В чем сила пакета singleflight?
🔸
🔸Если несколько запросов инициируют один и тот же вызов, это может привести к множественным идентичным вызовам к БД или API. Это создает нагрузку на систему, ведет к потерям CPU, памяти и пропускной способности сети.
🔸Matthew Boyle, автор Domain-Driven Design with Golang, приводит пример использования
💡
#tip
🔸
singleflight
предоставляет механизм подавления дублирующихся вызовов функций. Например, наше приложение запрашивает данные из API или базы данных. 🔸Если несколько запросов инициируют один и тот же вызов, это может привести к множественным идентичным вызовам к БД или API. Это создает нагрузку на систему, ведет к потерям CPU, памяти и пропускной способности сети.
🔸Matthew Boyle, автор Domain-Driven Design with Golang, приводит пример использования
singleflight
для устранения дублирующихся вызовов. В примере, несмотря на то, что 5 горутин одновременно запрашивают данные для одного и того же ключа, функция fetchData
будет вызвана только один раз благодаря функции group.Do
из пакета singleflight
.💡
singleflight
также может используется в serverless кейсах. Google App Engine, например, применяет его как часть функции инициализации, поскольку там нет main.go
.#tip
👍20🤔9🥱3❤1🔥1
Это позволяет обрабатывать данные построчно, упрощает использование CLI-инструментов (grep, awk, wc) и уменьшает неоднозначность по сравнению с CSV. Каждая строка JSONL может содержать более сложные данные, чем CSV-строка.
#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15😁14❤2
🤔 Могут ли адреса двух переменных быть одновременно равными и разными?
Код ниже определяет глобальную переменную
📌 Сравнение должно оцениваться как
Пример выводит:
Так что компилятор Go формально присваивает переменным нулевого размера адрес ради избежания введения специального случая переменных «без адреса». Компилятор может даже присвоить им один и тот же адрес, потому что для переменных нулевого размера не имеет значения, какой формальный адрес у них есть.
📌 Спецификация Go говорит:
#tip
Код ниже определяет глобальную переменную
a
и локальную переменную b
внутри main()
. Затем он выводит адреса обеих переменных и, наконец, сравнивает их.📌 Сравнение должно оцениваться как
true
, верно?var a struct{}
func main() {
var b struct{}
fmt.Printf("&a: %p\n", &a)
fmt.Printf("&b: %p\n", &b)
fmt.Println("&a == &b:", &a == &b)
}
Пример выводит:
&a: 0x58e360
&b: 0x58e360
&a == &b: false
a
и b
— это пустые структуры, которые имеют нулевой размер, следовательно, они не должны занимать ячейки памяти. Они вообще не должны иметь адрес. Так что сравнивать их адреса с самого начала не имеет смысла.Так что компилятор Go формально присваивает переменным нулевого размера адрес ради избежания введения специального случая переменных «без адреса». Компилятор может даже присвоить им один и тот же адрес, потому что для переменных нулевого размера не имеет значения, какой формальный адрес у них есть.
📌 Спецификация Go говорит:
Две различные переменные нулевого размера могут иметь одинаковый адрес в памяти
#tip
👍76🤔11❤3⚡2👾2
Value receivers и nil
Представьте себе структуру с двумя методами: один использует pointer receiver, а другой — value receiver.
Что происходит, если receiver равен
Переменная
Однако, если мы выполняем этот код, вызов
📌 Что происходит?
Рассмотрим, что методы — это просто функции с некоторым синтаксическим сахаром. Метод
Таким образом, вышеуказанный код может быть переписан без методов следующим образом:
Теперь должно быть легко понять, почему
Для метода
Так что, если у вас есть тип с pointer/value receivers, будьте осторожны, чтобы не вызывать какие-либо методы для
#tip
Представьте себе структуру с двумя методами: один использует pointer receiver, а другой — value receiver.
package main
type S struct {
N int
}
func (s *S) PointerRcv() {
}
func (s S) ValueRcv() {
}
Что происходит, если receiver равен
nil
?
func main() {
var s *S // s равно nil
s.PointerRcv()
s.ValueRcv()
}
Переменная
s
принимает нулевое значение типа *S
, которое является nil
. Поскольку ни один из методов не обращается к receiver'у, оба вызова метода должны пройти без проблем.Однако, если мы выполняем этот код, вызов
s.ValueRcv()
вызовет панику!📌 Что происходит?
Рассмотрим, что методы — это просто функции с некоторым синтаксическим сахаром. Метод
func (s S) f()
семантически идентичен функции func f(s S)
. Method receiver становится первым аргументом функции.Таким образом, вышеуказанный код может быть переписан без методов следующим образом:
package main
type S struct {
N int
}
func PointerFunc(s *S) {
}
func ValueFunc(s S) {
}
func main() {
var s *S
PointerFunc(s)
ValueFunc(*s)
}
Теперь должно быть легко понять, почему
ValueFunc()
вызывает панику. Указатель s
должен быть разыменован при передаче его в ValueFunc()
. Разыменование nil
указателя невозможно и приводит к панике.Для метода
func (s *S) PointerRcv()
, receiver (или параметр функции во втором примере) не нуждается в разыменовании. Следовательно, паники не будет.Так что, если у вас есть тип с pointer/value receivers, будьте осторожны, чтобы не вызывать какие-либо методы для
nil
значения этого типа.#tip
👍49🥱6❤1
💡 Если у вас запущено множество локальных серверов, и вам надоело обращаться к ним как
Предположим, у вас есть локальный сервер на порту 9000. После установки Caddy, выполните команду:
и откройте https://myserver.localhost. Вы увидите, что сервер на
А если вы хотите проксировать больше серверов таким образом, создайте файл с именем
#tip
localhost:8081
, localhost:9000
и т. д., посмотрите в сторону Caddy. Он сделает настройку «доменов» для локальных серверов проще простого.Предположим, у вас есть локальный сервер на порту 9000. После установки Caddy, выполните команду:
caddy reverse-proxy --from myserver.localhost --to :9000
и откройте https://myserver.localhost. Вы увидите, что сервер на
localhost:9000
отвечает. Caddy даже предоставляет локальные TLS-сертификаты. А если вы хотите проксировать больше серверов таким образом, создайте файл с именем
Caddyfile
и введите конфигурацию хоста следующим образом:
myapp.localhost {
reverse-proxy :9000
}
myhugoblog.localhost {
reverse-proxy :1313
}
#tip
❤31👏11👍8🔥4
Вы наверняка знакомы со «стандартным» способом ветвления кода в зависимости от заданного значения:
Так работает
Но оператор
1. Несколько значений в одном
В
Заданное значение может использоваться только в одном блоке
2. Инициализатор, как в цикле
Вы можете инициализировать значение перед использованием его в
3. Нет выражения
Если текущее значение
4. Переключение по типу переменной.
Если ваш код получает значение интерфейса из какого-то источника, вы можете использовать переключение по типу, чтобы проверить фактический тип этого значения:
5. Переключение по типу параметра.
Это может показаться немного эзотерическим: вы можете определить дженерик функцию, где типовой параметр не используется в списке параметров. Вместо этого оператор
Как и с выражениями
👉 Go Playground
#tip
switch a {
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("default")
}
Так работает
switch
в Go и во многих других языках (за исключением того, что в Go не происходит перехода к последующим case
).Но оператор
switch
может делать больше. Вот несколько кейсов.1. Несколько значений в одном
case
.В
case
можно указать несколько значений для сопоставления:
switch a {
case 1:
fmt.Println("1")
case 2, 3, 4:
fmt.Println("2, 3 или 4")
// case 1,2: // ошибка: дублирование case 1, дублирование case 2
// fmt.Println("1 или 2")
}
Заданное значение может использоваться только в одном блоке
case
. Дублирование значений в case
вызовет ошибку.2. Инициализатор, как в цикле
for
.Вы можете инициализировать значение перед использованием его в
switch
. Область видимости переменной a
ограничена конструкцией switch
:
switch a := f(); a {
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
}
3. Нет выражения
switch
, но есть выражения case
.case
не ограничен статическими значениями. Если вы опустите выражение switch
, вы можете использовать выражения для каждого case
:
switch {
case a == 1:
fmt.Println("1")
case a >=2 && a <= 4:
fmt.Println("2")
case a <= 5:
fmt.Println("3")
}
Если текущее значение
a
совпадает более чем с одним case
, выбирается первый подходящий.4. Переключение по типу переменной.
Если ваш код получает значение интерфейса из какого-то источника, вы можете использовать переключение по типу, чтобы проверить фактический тип этого значения:
switch v := a.(type) {
case int:
fmt.Println("a — это int:", v)
case string, []byte:
fmt.Println("a — это string:", v)
}
5. Переключение по типу параметра.
Это может показаться немного эзотерическим: вы можете определить дженерик функцию, где типовой параметр не используется в списке параметров. Вместо этого оператор
switch
может ссылаться на этот параметр, чтобы проверить значение параметра:
func do[T comparable](a any) {
switch v := a.(type) {
case int:
fmt.Println("a — это int:", v)
case T:
fmt.Printf("a — это %T: %v", v, v)
case []T:
fmt.Println("a — это срез:", v)
case []byte:
fmt.Println("a — это срез байт:", v)
}
}
func main() {
do[bool](a)
do[bool](true)
do[int]([]int{1, 2, 3})
}
Как и с выражениями
case
, если фактический тип a
совпадает с несколькими case
, выбирается первый подходящий.👉 Go Playground
#tip
go.dev
Go Playground - The Go Programming Language
👍60❤7🥱5🔥3😍1
Флаг
запустит тесты 2 раза. Тесты сначала будут запущены с четырьмя процессорами, а затем второй раз — с пятью.
#tip
-cpu
можно использовать при запуске тестов Go, чтобы указать список значений GOMAXPROCS
, с использованием которых необходимо запустить тесты. Например,go test -cpu=4,5
запустит тесты 2 раза. Тесты сначала будут запущены с четырьмя процессорами, а затем второй раз — с пятью.
#tip
🔥39👍10❤4
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🤔3
Кстати, функция
👉 Go Playground
#tip
LookupEnv
в Go может использоваться для определения того, установлена ли переменная окружения или нет. 👉 Go Playground
#tip
🥱25👍22🌚3👏1
🚀 Оптимизация и стресс-тесты в Go с флагом -cpu
Когда речь идет о тестировании производительности и устойчивости приложений, работающих в многопоточной среде, Go предоставляет отличный инструмент — флаг -cpu. Этот параметр позволяет запускать тесты с разным количеством логических процессоров (GOMAXPROCS), моделируя реальное поведение приложения в условиях разного уровня параллелизма.
📌 Как работает флаг -cpu?
Флаг -cpu указывается при запуске тестов и задаёт список значений, с которыми тесты должны быть выполнены. Например:
👉 Этот пример запустит тесты дважды:
1️⃣ С четырьмя логическими процессорами.
2️⃣ Затем с пятью.
🛠 Зачем это использовать?
➖ Тестирование под разной нагрузкой: использование нескольких значений -cpu позволяет понять, как ваше приложение ➖ поведёт себя на системах с разным количеством ядер.
➖ Поиск узких мест: помогает выявить проблемы в конкурентном доступе, такие как гонки данных или узкие места в производительности.
➖ Реализм тестов: ваш код проверяется в условиях, максимально приближенных к реальной эксплуатации.
🔑 Ключевые моменты:
➖ Можно указать несколько значений через запятую, например -cpu=1,2,4,8, чтобы протестировать приложение в разнообразных сценариях.
➖ Если -cpu не задан, тесты запускаются с текущим значением GOMAXPROCS.
Практическая выгода: Регулярное использование -cpu в тестах повышает устойчивость вашего кода и предотвращает неожиданные проблемы при высоких нагрузках.
💡 Пример для продвинутых:
Если вы хотите протестировать код на нескольких уровнях параллелизма, запустите:
📊 Результат: тесты покажут, как приложение справляется с 1, 2, 4 и 8 логическими процессорами. Это отличный способ убедиться, что ваш код работает эффективно и безопасно в конкурентной среде.
#tip
Когда речь идет о тестировании производительности и устойчивости приложений, работающих в многопоточной среде, Go предоставляет отличный инструмент — флаг -cpu. Этот параметр позволяет запускать тесты с разным количеством логических процессоров (GOMAXPROCS), моделируя реальное поведение приложения в условиях разного уровня параллелизма.
Флаг -cpu указывается при запуске тестов и задаёт список значений, с которыми тесты должны быть выполнены. Например:
go test -cpu=4,5
👉 Этот пример запустит тесты дважды:
🛠 Зачем это использовать?
🔑 Ключевые моменты:
Практическая выгода: Регулярное использование -cpu в тестах повышает устойчивость вашего кода и предотвращает неожиданные проблемы при высоких нагрузках.
Если вы хотите протестировать код на нескольких уровнях параллелизма, запустите:
go test -cpu=1,2,4,8 -v
📊 Результат: тесты покажут, как приложение справляется с 1, 2, 4 и 8 логическими процессорами. Это отличный способ убедиться, что ваш код работает эффективно и безопасно в конкурентной среде.
#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
👍40❤6😁2❤🔥1