GO

Информация по append в Go

append — одна из самых важных встроенных функций в Go для работы со слайсами. Ниже рассмотрены все возможные способы использования, примеры и пояснения.


🔧 Базовый синтаксис

append(slice, elems...)

✅ Варианты использования

1. Добавить один элемент

nums := []int{1, 2}
nums = append(nums, 3)
// → [1 2 3]

2. Добавить несколько элементов

nums = append(nums, 4, 5, 6)
// → [1 2 3 4 5 6]

3. Добавить другой срез

more := []int{7, 8}
nums = append(nums, more...) // обязательно `...`!

4. Добавить пустой срез

nums = append(nums, []int{}...) // всё ещё [1 2 3 4 5 6 7 8]

5. Добавить к nil-срезу

var s []int
s = append(s, 10) // работает, даже если s == nil

6. Удалить элемент (по индексу)

i := 2
s = append(s[:i], s[i+1:]...)

📐 Как работает append


🛠 Трюки с append

🔁 Объединение слайсов

a := []string{"a", "b"}
b := []string{"c", "d"}
a = append(a, b...) // → [a b c d]

🪓 Удаление последнего элемента

s = s[:len(s)-1]

⚠️ Важно помнить


🧠 Сводка

Использование Пример
Один элемент append(s, 10)
Несколько элементов append(s, 1, 2, 3)
Добавить срез append(s, other...)
Удалить элемент append(s[:i], s[i+1:]...)
Работать с nil-срезом append(nil, 1)

Как устроен планировщик горутин в Go: G-M-P модель

Go использует уникальную модель управления конкурентностью — G-M-P (Goroutine, Machine, Processor), которая обеспечивает масштабируемость, высокую производительность и лёгкость работы с параллелизмом.


🧠 Компоненты Go-планировщика

Обозначение Название Что это такое
G Goroutine Логическая единица выполнения (код + стек)
M Machine (Thread) Системный поток (OS thread)
P Processor Планировщик, управляющий выполнением G

🔄 Как это работает?

M нужен P, чтобы выполнять G. Без P поток (M) простаивает.


🔁 Модель в действии

При запуске:

runtime.GOMAXPROCS(4)

Ты создаёшь 4 P (процессора). Это означает, что Go может одновременно запускать до 4 горутин в реальном параллелизме.


🧬 Визуально:

+-------------+        +----------+        +------------------+
| Goroutine G |   -->  |   P      |  -->   | Thread M (OS)    |
+-------------+        +----------+        +------------------+
       ^                     |
       |_____________________|
         Work stealing (если P простаивает)

⏳ Work-stealing

Если один P (процессор) простаивает, он может “украсть” G из очереди другого P. Это делает систему ещё более гибкой и минимизирует простои.


📌 Примеры поведения

Сценарий Как себя ведёт Go
1 млн горутин Go спокойно справится
Ожидание по time.Sleep P отходит от M и отдаёт другим
Блокировка I/O M блокируется, но P берёт новый M
runtime.GOMAXPROCS(n) Ограничивает кол-во одновременно исполняемых G

✅ Вывод:

Go-планировщик:

Сравнение: Мьютексы и Каналы в Go

Go предлагает два подхода к синхронизации и обмену данными между горутинами:


🔒 Мьютексы (sync.Mutex)

Мьютекс позволяет гарантировать, что только одна горутина в момент времени имеет доступ к критической секции кода.

✅ Пример:

import "sync"

type Counter struct {
    m  sync.Mutex
    v  int
}

func (c *Counter) Inc() {
    c.m.Lock()
    c.v++
    c.m.Unlock()
}

🧠 Когда использовать мьютекс:


📬 Каналы (chan)

Каналы передают данные между горутинами. Это позволяет избежать прямого доступа к разделяемой памяти.

✅ Пример:

func worker(jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 5)
    results := make(chan int, 5)

    go worker(jobs, results)

    jobs <- 1
    jobs <- 2
    close(jobs)

    fmt.Println(<-results)
    fmt.Println(<-results)
}

🧠 Когда использовать каналы:


⚖️ Сравнение

Критерий Мьютексы Каналы
Принцип Общая память Сообщения
Сложность Низкая, но с рисками Чище, но требует дизайна
Поддержка pprof Да (через mutex) Частично (горутины)
Предотвращение гонок Да Да
Применение Быстрый доступ Асинхронные операции

🧪 Рекомендация от Go-разработчиков

“Не общайся через общую память — делись памятью через общение.”

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


📌 Итог

| Хочешь максимальную скорость | Используй sync.Mutex |
| Хочешь безопасный обмен | Используй chan |
| Хочешь лучшее из двух миров | Используй sync.Mapsync.Oncesync.WaitGroup при необходимости |


🧰 Дополнительные инструменты из sync

Go также предоставляет удобные примитивы для управления конкурентностью:


🔁 sync.Once — выполнить только один раз

Позволяет гарантировать, что определённый код будет выполнен только один раз, даже если вызывается из нескольких горутин.

var once sync.Once

func initConfig() {
    once.Do(func() {
        fmt.Println("Конфигурация инициализирована")
    })
}

Используется для ленивой инициализации, подключения к БД, загрузки конфигов.


👥 sync.WaitGroup — дождаться завершения горутин

Позволяет дождаться, пока все горутины завершатся.

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        fmt.Printf("Горутина %d завершена
", id)
    }(i)
}

wg.Wait()
fmt.Println("Все горутины завершились")

🗺️ sync.Map — потокобезопасная map

Альтернатива обычной map, не требующая ручной синхронизации.

var sm sync.Map

sm.Store("foo", 42)
val, ok := sm.Load("foo")
if ok {
    fmt.Println("Значение:", val)
}

sm.Range(func(key, value any) bool {
    fmt.Printf("%v = %v
", key, value)
    return true
})

Подходит для кэширования, счётчиков, безопасной общей памяти между горутинами.


📌 Вывод:

Инструмент Назначение
sync.Mutex Защита разделяемых данных
sync.Once Инициализация кода только один раз
sync.WaitGroup Ожидание завершения группы горутин
sync.Map Потокобезопасный ассоциативный массив

Поведение for range с каналами в Go

В Go for v := range ch используется для последовательного чтения из канала ch. Цикл завершится только после того, как:


🔁 Сценарии поведения

1. Канал пуст и не закрыт

ch := make(chan string)

for v := range ch {
    fmt.Println(v)
}

📌 Итог: блокировка навсегда, цикл ждёт значения, но никто не пишет.


2. В канале есть одно значение, канал не закрыт

ch := make(chan string)

go func() {
    ch <- "hello"
}()

for v := range ch {
    fmt.Println(v)
}

📌 Итог: hello выведется, затем цикл зависнет, ожидая следующего значения.


3. В канале есть значение, потом ещё одно, затем закрытие

ch := make(chan string)

go func() {
    ch <- "one"
    time.Sleep(1 * time.Second)
    ch <- "two"
    close(ch)
}()

for v := range ch {
    fmt.Println(v)
}

📌 Итог:


✅ Правильное завершение горутины через канал и WaitGroup

import (
    "fmt"
    "sync"
)

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)
    }
}

func main() {
    jobs := make(chan int)
    var wg sync.WaitGroup

    for w := 1; w <= 2; w++ {
        wg.Add(1)
        go worker(w, jobs, &wg)
    }

    for j := 1; j <= 5; j++ {
        jobs <- j
    }

    close(jobs)  // 🔑 важно закрыть канал
    wg.Wait()    // 🔒 ждём завершения всех воркеров
}

🧠 Общее поведение range по каналам

Сценарий Поведение
Канал пуст и не закрыт ❌ Блокировка
Есть 1 значение, канал не закрыт ✅ Прочтёт 1, потом зависнет
Есть значения, канал закрыт после них ✅ Прочтёт все, завершит цикл
Канал закрыт сразу ✅ Завершит цикл (если пуст)

📌 Рекомендации

Пример потока данных в Clean Architecture на Go

Предисловие

Clean Architecture (Чистая Архитектура), предложенная Робертом Мартином (Uncle Bob), — это подход к проектированию ПО, который подчеркивает разделение на слои с четкими зависимостями.

Основная идея: сделать систему независимой от фреймворков, UI, баз данных и внешних сервисов. Зависимости направлены “внутрь” — внешние слои зависят от внутренних, но не наоборот.

Ключевые слои Clean Architecture:

Практический пример: Создание пользователя

Сценарий: Пользователь регистрируется, отправляя HTTP POST-запрос на /users с JSON-телом:

{
  "name": "Alice Johnson",
  "email": "alice@example.com"
}

Приложение использует PostgreSQL как БД. Рассмотрим пошаговый поток данных:

image.png

Входящий поток: От запроса к сохранению в БД

1. HTTP запрос → Адаптер (HTTP Handler)

Что происходит: Адаптер получает сырой HTTP запрос и парсит его.

2. Адаптер → Контроллер

Что происходит: Контроллер валидирует данные.

3. Контроллер → UseCase

Что происходит: UseCase применяет бизнес-правила и проверяет уникальность.

4. UseCase → Repository Interface

Что происходит: Абстрактный вызов сохранения.

5. Repository Interface → Driver (PostgreSQL)

Что происходит: Формирование и выполнение SQL-запроса.

6. Database → Успешный ответ

Что происходит: Сохранение в PostgreSQL.


Визуализация полного цикла на диаграмме

📊 Прямые стрелки (сплошные): Входящий поток

Движение данных ОТ клиента К базе данных

📊 Обратные стрелки (пунктирные): Исходящий поток

Движение результата ОТ базы данных К клиенту


Ключевые преимущества архитектуры

  1. Изоляция слоев: Смена БД не затрагивает бизнес-логику
  2. Тестируемость: UseCase можно тестировать с mock-репозиториями
  3. Понятность: Каждый слой имеет четкую ответственность
  4. Гибкость: Легко добавлять новые адаптеры (GraphQL, gRPC)

Заключение

Этот пример показывает, как Clean Architecture делает поток данных предсказуемым и устойчивым. Каждый слой выполняет свою роль, а зависимости направлены правильно.