В Go строки представлены как набор байтов (
[]byte
), но количество символов может отличаться, так как некоторые символы занимают несколько байтов (например, Unicode). len(s)
– количество байтов (НЕ символов!)s := "Привет"
fmt.Println(len(s)) // 12, потому что кириллические символы занимают 2 байта
utf8.RuneCountInString(s)
– количество символов (РЕКОМЕНДУЕТСЯ )package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Привет"
fmt.Println(utf8.RuneCountInString(s)) // 6, правильно считает символы
}
Подсчёт через
range
(альтернативный метод)count := 0
for range "Привет" {
count++
}
fmt.Println(count) // 6
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Слайсы небезопасны для одновременного изменения. Их безопасно использовать только для чтения или с синхронизацией.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Один из полезных паттернов, который я использовал в реальном проекте на Go — это Worker Pool (пул воркеров).
Этот паттерн помогает ограничить количество горутин при обработке задач, чтобы не перегружать систему.
В моём проекте был сервис, который обрабатывал тысячи файлов параллельно. Если запускать новую горутину на каждый файл, то память быстро заканчивалась, и сервер "умирал".
Решение: вместо создания тысяч горутин я использовал Worker Pool с фиксированным числом воркеров (например, 5), которые брали задачи из очереди.
Есть канал задач (
jobs
). Есть пул воркеров (
workers
), которые читают задачи из канала. Каждый воркер обрабатывает одну задачу и берёт следующую.
package main
import (
"fmt"
"sync"
"time"
)
// Количество воркеров
const workerCount = 5
// Воркер, который берет задачи из канала и обрабатывает их
func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second) // Имитация обработки
}
}
func main() {
jobs := make(chan int, 10)
var wg sync.WaitGroup
// Запускаем воркеров
for i := 1; i <= workerCount; i++ {
wg.Add(1)
go worker(i, jobs, &wg)
}
// Отправляем задачи в канал
for j := 1; j <= 10; j++ {
jobs <- j
}
close(jobs) // Закрываем канал, чтобы воркеры знали, что задачи кончились
wg.Wait() // Ждём завершения всех воркеров
fmt.Println("Все задачи обработаны")
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Сериализация — это процесс преобразования объекта или структуры в формат, пригодный для хранения или передачи (например, в JSON, XML, бинарный формат).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Интеграционные тесты — это тесты, которые проверяют взаимодействие между различными компонентами системы. В отличие от юнит-тестов, которые фокусируются на проверке отдельных функций или методов, интеграционные проверяют, как компоненты работают вместе, обеспечивая целостность и корректность всей системы.
Интеграционные тесты помогают убедиться, что различные части системы правильно взаимодействуют друг с другом. Это важно для выявления проблем на границах между компонентами, которые могут не проявиться в юнит-тестах.
Такие тесты помогают обнаруживать ошибки, возникающие при объединении модулей. Это могут быть проблемы с форматами данных, неправильное использование интерфейсов и другие ошибки, связанные с интеграцией.
Интеграционные тесты могут включать сценарии, приближенные к реальным условиям эксплуатации системы, что позволяет убедиться, что система работает правильно в реальной среде.
Интеграционные тесты могут требовать наличия нескольких зависимостей, таких как база данных, внешние API или другие сервисы. Настройте тестовую среду, которая будет эмулировать реальную среду.
// main.go
package main
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
"log"
)
type User struct {
ID int
Name string
}
func CreateUser(db *sql.DB, name string) (int, error) {
res, err := db.Exec("INSERT INTO users(name) VALUES(?)", name)
if err != nil {
return 0, err
}
id, err := res.LastInsertId()
if err != nil {
return 0, err
}
return int(id), nil
}
func GetUser(db *sql.DB, id int) (User, error) {
var user User
err := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name)
if err != nil {
return user, err
}
return user, nil
}
func main() {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
if err != nil {
log.Fatal(err)
}
}
// main_test.go
package main
import (
"database/sql"
"testing"
_ "github.com/mattn/go-sqlite3"
)
func setupTestDB(t *testing.T) *sql.DB {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
_, err = db.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
if err != nil {
t.Fatal(err)
}
return db
}
func TestCreateAndGetUser(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// Создание пользователя
userID, err := CreateUser(db, "John Doe")
if err != nil {
t.Fatalf("Failed to create user: %v", err)
}
// Получение пользователя
user, err := GetUser(db, userID)
if err != nil {
t.Fatalf("Failed to get user: %v", err)
}
// Проверка результатов
if user.Name != "John Doe" {
t.Errorf("Expected name to be 'John Doe', got %s", user.Name)
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
copy
? 1. Синтаксис: copy(dst, src).
2. Копируется минимальное количество элементов, равное длине меньшего слайса.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это версии протокола HTTP, каждая из которых имеет свои особенности и улучшения по сравнению с предыдущими версиями. Важные различия между этими версиями включают следующие аспекты:
Поддерживает одновременное открытие нескольких TCP соединений (обычно 6-8), что позволяет загружать несколько ресурсов параллельно. Однако каждое соединение может обрабатывать только один запрос за раз, что приводит к задержкам из-за блокировки очереди (head-of-line blocking).
Вводит мультиплексирование, позволяющее отправлять множество запросов и ответов асинхронно через одно единственное TCP соединение. Это значительно уменьшает задержки и улучшает производительность при загрузке страниц с большим количеством ресурсов.
Является текстовым протоколом, что означает, что запросы и ответы форматируются в виде читаемого текста.
Бинарный протокол, который делает передачу данных более эффективной и менее подверженной ошибкам в синтаксическом анализе. Бинарный формат упрощает реализацию парсеров и уменьшает размер передаваемых данных.
Заголовки передаются без сжатия, что может привести к значительному объему передаваемых данных, особенно если одни и те же заголовки отправляются повторно с каждым запросом.
Использует механизм сжатия заголовков HPACK, который уменьшает избыточность заголовков, сжимая их перед отправкой. Это особенно эффективно для повторяющихся запросов к одним и тем же серверам.
Не поддерживает приоритизацию запросов, из-за чего браузеры должны использовать эвристики для управления приоритетами ресурсов.
Поддерживает явную приоритизацию запросов, позволяя клиенту указывать приоритет обработки ресурсов, что делает загрузку страниц более эффективной.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Агрегатные функции работают с группами данных и возвращают один результат:
- COUNT() — количество.
- SUM() — сумма.
- AVG() — среднее.
- MIN(), MAX() — минимум и максимум.
- В некоторых СУБД есть STRING_AGG(), GROUP_CONCAT() — объединение строк.
Они часто используются с GROUP BY в SQL и при анализе данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В Go методы создаются для структур (или типов) внутри пакета. Это позволяет добавлять логику и поведение объектам.
Допустим, мы создаем пакет
mathutils
, который будет содержать метод для структуры Calculator
.package mathutils
// Calculator - структура с данными
type Calculator struct {
A, B int
}
// Sum - метод для сложения чисел A и B
func (c Calculator) Sum() int {
return c.A + c.B
}
Теперь мы можем использовать этот метод в основном файле программы.
package main
import (
"fmt"
"mypackage/mathutils" // Импортируем наш пакет
)
func main() {
calc := mathutils.Calculator{A: 5, B: 3}
fmt.Println("Sum:", calc.Sum()) // Выведет: Sum: 8
}
Методы можно объявлять как для значений (
func (c Calculator)
) так и для указателей (func (c *Calculator)
). Когда использовать указатели?
Если метод изменяет данные структуры.
Чтобы избежать копирования больших структур.
func (c *Calculator) Multiply(factor int) {
c.A *= factor
c.B *= factor
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Синхронизация необходима для предотвращения конфликтов при одновременном доступе из нескольких потоков (или горутин). Без синхронизации возможны:
- Повреждение данных
- Гонки (data race)
- Непредсказуемое поведение и ошибки
Примитивы синхронизации (mutex, atomic и т.д.) обеспечивают корректность и согласованность данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Go — это императивный язык программирования.
Императивное программирование означает, что программист явно указывает шаги, которые необходимо выполнить для достижения результата. Код в Go представляет собой последовательность команд, изменяющих состояние программы.
package main
import "fmt"
func main() {
sum := 0
for i := 1; i <= 5; i++ {
sum += i // Явно изменяем переменную sum
}
fmt.Println("Сумма:", sum)
}
Хотя Go — это в первую очередь императивный язык, в нем есть элементы декларативного подхода. Например:
map
, filter
через срезы и функции высшего порядка. интерфейсы позволяют писать код, в котором детали реализации скрыты (инкапсуляция).
вместо явного управления потоками, мы декларируем взаимодействие через каналы.
package main
import "fmt"
func mapSlice(slice []int, f func(int) int) []int {
result := make([]int, len(slice))
for i, v := range slice {
result[i] = f(v) // Декларативное применение функции к элементам
}
return result
}
func main() {
nums := []int{1, 2, 3, 4, 5}
squared := mapSlice(nums, func(n int) int { return n * n })
fmt.Println(squared) // [1 4 9 16 25]
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Значения, передаваемые в defer, фиксируются в момент объявления defer, а не в момент выполнения.
Если ты передаёшь результат выражения, он вычисляется сразу, а отложенный вызов запоминает результат.
Но если используется именованная возвращаемая переменная, и она изменяется внутри defer, то её новое значение попадает в результат. Это позволяет, например, изменить результат функции прямо из defer-блока.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Линтеры — это инструменты для автоматической проверки кода на ошибки, потенциальные баги и несоответствие стилю кодирования.
В Go есть несколько популярных линтеров:
golangci-lint
– самый мощный и популярный, объединяет множество линтеров. go vet
– стандартный инструмент для поиска ошибок. golint
– проверяет стиль кода (но устарел). staticcheck
– анализирует код на ошибки и неэффективность. Устанавливаем
golangci-lint
(лучший вариант) go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
После установки проверьте версию:
golangci-lint --version
Запустить проверку в проекте можно так:
golangci-lint run
Можно проверить только определённый файл:
golangci-lint run myfile.go
Если хотите автоматически исправлять ошибки, используйте:
golangci-lint run --fix
Go уже имеет встроенный линтер
go vet ./...
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это ситуация, когда несколько горутин (или потоков) находятся в состоянии ожидания друг друга, из-за чего выполнение программы останавливается. В Go deadlock может произойти при неправильной работе с каналами, мьютексами и другими механизмами синхронизации.
Если несколько горутин используют блокировки (например, через мьютексы), убедитесь, что все они захватывают их в одном и том же порядке.
func main() {
var mu1, mu2 sync.Mutex
go func() {
mu1.Lock()
defer mu1.Unlock()
mu2.Lock()
defer mu2.Unlock()
}()
go func() {
mu2.Lock()
defer mu2.Unlock()
mu1.Lock()
defer mu1.Unlock()
}()
}
Каналы должны всегда иметь возможность отправки и получения данных. Если одна сторона (отправитель или получатель) заблокирована навсегда, возникает deadlock.
func main() {
ch := make(chan int)
ch <- 42 // Deadlock, так как никто не читает из канала
}
Каналы нужно закрывать только со стороны отправителя, и только тогда, когда больше не будет отправок данных. Неправильное закрытие или отсутствие закрытия может привести к проблемам, включая deadlock.
ch := make(chan int)
close(ch) // Закрыт слишком рано
ch <- 42 // Паника
Иногда deadlock происходит, если горутина ждет сама себя.
func main() {
ch := make(chan int)
ch <- 1
fmt.Println(<-ch) // Никогда не выполнится
}
Go позволяет избегать блокировок с помощью механизма тайм-аутов и оператора
select
. Если операция занимает слишком много времени, можно выполнить альтернативное действие.func main() {
ch := make(chan int)
select {
case data := <-ch:
fmt.Println("Получены данные:", data)
case <-time.After(1 * time.Second):
fmt.Println("Тайм-аут, завершение")
}
}
Go предоставляет утилиту
go run -race
, которая помогает выявлять гонки данных и другие проблемы, связанные с синхронизацией.go run -race main.go
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1. Горутина ждет данные, а никто не пишет в канал (<-chan, но нет chan <-).
2. Основная горутина завершилась, а другие ждут завершения.
3. Все горутины заблокированы на ожидании данных (select { case <-ch1: case <-ch2: } – если ни один не отправляет данные).
4. Закрыли канал, но кто-то пытается в него записать – вызывает panic.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В Go эффективное объединение строк – важная задача, поскольку строки неизменяемые. Неправильный подход (например, простая конкатенация
s1 + s2 + s3
) может привести к множественным аллокациям памяти и копированиям. Использование
strings.Builder
(Рекомендуется)Это самый эффективный способ склеивания строк, так как он минимизирует количество аллокаций.
package main
import (
"fmt"
"strings"
)
func main() {
var sb strings.Builder
sb.WriteString("Hello")
sb.WriteString(", ")
sb.WriteString("World!")
result := sb.String()
fmt.Println(result) // Hello, World!
}
Использование
+
(Неэффективно )s := "Hello" + ", " + "World!"
Использование
fmt.Sprintf
(Неэффективно )s := fmt.Sprintf("%s, %s!", "Hello", "World")
Использование
strings.Join
(Хорошо для срезов )Если строки хранятся в
[]string
, strings.Join
– это оптимальный вариантpackage main
import (
"fmt"
"strings"
)
func main() {
words := []string{"Hello", "World", "Go"}
result := strings.Join(words, ", ")
fmt.Println(result) // Hello, World, Go
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это состояние, при котором все горутины ожидают события, которое не наступит. Go детектирует deadlock и вызывает panic, если основная горутина заблокирована на ожидании данных из канала, но нет активных писателей.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Mutex (от Mutual Exclusion — взаимное исключение) — это примитив синхронизации, который используется для защиты критических секций ресурса или данных, доступ к которым должен быть ограничен таким образом, чтобы только один поток или горутина могли работать с ним в каждый конкретный момент времени. Mutex гарантирует, что только один поток может входить в защищённую секцию кода, выполняющую операции над общими данными.
Это наиболее распространённый тип мьютексов. При попытке захватить мьютекс, если он уже занят другим потоком, поток блокируется и ожидает, пока мьютекс не будет освобождён.
Вместо блокирования потока, потоки активно проверяют состояние мьютекса в цикле. Это может быть эффективно, если мьютекс захватывается на очень короткое время, так как избегается затрата времени на блокировку и разблокировку потока.
Стандартная библиотека предоставляет мьютекс в пакете
sync
. Вот пример того, как можно использовать мьютекс для синхронизации доступа к общему ресурсу:package main
import (
"fmt"
"sync"
)
var (
balance int
mutex sync.Mutex
)
func deposit(value int, wg *sync.WaitGroup) {
mutex.Lock() // Захват мьютекса перед изменением переменной balance
fmt.Printf("Depositing %d to account with balance: %d\n", value, balance)
balance += value
mutex.Unlock() // Освобождение мьютекса после изменения
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go deposit(100, &wg)
go deposit(200, &wg)
wg.Wait()
fmt.Printf("New Balance %d\n", balance)
}
Держите код внутри защищённой секции как можно более коротким, чтобы избежать замедления работы других потоков, ожидающих доступа к ресурсу.
Это может привести к взаимоблокировкам, если мьютексы захватываются в разном порядке.
Помните, что мьютексы защищают только блоки кода, для которых они явно захватываются. Доступ к тем же данным за пределами защищённого блока может привести к условиям гонки.
Если это возможно, используйте каналы или другие средства синхронизации, которые могут быть более идиоматичными.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Горутины управляются Go runtime, а не операционной системой напрямую.
Это делает их лёгкими и дешевыми — можно создавать тысячи горутин без нагрузки на систему.
ОС видит лишь ограниченное количество потоков (M), а Go сам планирует, какие G выполнять на каких M через P.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Слайс (
slice
) — это динамический массив, который ссылается на часть массива в памяти. В отличие от массивов (array
), слайсы могут изменять размер. Слайс в Go — это структура
type SliceHeader struct {
Data uintptr // Указатель на массив в памяти
Len int // Длина слайса (количество элементов)
Cap int // Вместимость (capacity) — сколько элементов может вместить без перевыделения памяти
}
Пример структуры слайса
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // Берём срез от 2-го до 4-го элемента
fmt.Println(s) // [2 3 4]
Есть несколько способов создать слайс:
Способ 1: Срез массива
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:4] // [20 30 40]
Способ 2: Использование
make()
s := make([]int, 3, 5) // Длина 3, вместимость 5
fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 5
Способ 3: Литерал (инициализация значениями)
s := []int{1, 2, 3}
fmt.Println(s) // [1 2 3]
Слайсы можно изменять, используя
append()
. s := []int{1, 2, 3}
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]
Когда
append()
увеличивает slice
, Go использует оптимизированный алгоритм роста:- Если
cap < 1024
, слайс удваивает размер (cap *= 2
). - Если
cap >= 1024
, рост идёт примерно на 25% (cap += cap / 4
). s := []int{}
for i := 0; i < 10; i++ {
s = append(s, i)
fmt.Printf("Len: %d, Cap: %d\n", len(s), cap(s))
}
Выход (пример)
Len: 1, Cap: 1
Len: 2, Cap: 2
Len: 3, Cap: 4
Len: 5, Cap: 8
Len: 9, Cap: 16
Так как слайсы хранят ссылку на массив, возможны побочные эффекты.
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[:3] // [1 2 3]
s2 := arr[2:] // [3 4 5]
s2[0] = 100 // Меняем первый элемент s2
fmt.Println(s1) // [1 2 100] ❗️ s1 тоже изменился
Решение: используйте
copy()
для создания нового массива. s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1) // Копируем данные
s2[0] = 100
fmt.Println(s1) // [1 2 3] ✅ Оригинал не изменился
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM