Содержание

Основы Golang

Содержание

Golang в кратком изложении. Шпаргалка по базовым положениям.

Замечание
Это короткий конспект по языку Golang. Если вы новичок и хотите выучить язык с абсолютного нуля, то рекомендую обратиться к Golang tutorial.

Go — это компилируемый, статически типизированный язык программирования, разработанный Google. Первая стабильная версия была выпущена в 2012 году.

1. Объявление переменных

Явное объявление (ключевое слово var):

var car string
var name string = "John"
var age int = 25
var isStudent bool = false

Неявное объявление (через присвоение :=):

// Возможно во внутренней функции
name := "Alice"  // Тип определен как string
age := 30        // Тип определен как int
isStudent := true // bool

Имя переменной — это произвольный идентификатор, состоящий из букв, цифр и символа подчеркивания (_). Также нельзя использовать зарезервированные слова.

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

2. Базовые типы в Go

2.1. Примитивные типы

ТипОписаниеМин./макс. значениеЗн. по умолч
boolBoolean (true, false)false / truefalse
stringНеизменяемая последовательность символов"" / N/A""
intЦелое число (платформо-зависимое)-2^63 to 2^63-1 (64-bit)0
int8-128 to 1270
int16-32,768 to 32,7670
int32-2,147,483,648 to 2,147,483,6470
int64-2^63 to 2^63-10
uintБеззнаковое целое число (п.-з.)0 to -2^64 (64-bit)0
uint80 to 2550
uint160 to 65,5350
uint320 to 4,294,967,2950
uint640 to 2^64 - 10
float32, float64Число с плавующей точкой~2.3E-308 to ~1.8E3080.0
complex64, complex128Комплексное числоN/A(0+0i)
byteБайт (псевдоним uint8)0 to 2550
runeUnicode символ (псевдоним uint32)0 to 2^31-10

2.2. Составные типы

ТипОписаниеРазмер (в байтах)По умолчанию
arrayКоллекция фиксированного размераЗависит от типа элементов и длины[0,0,...]
sliceКоллекция динамического размера (на основе массива)24 (на 64-битной системе)nil
mapХранилище ключ-значениеВарьируется (зависит от ключей и значений)nil
structПользовательский тип данных (группировка полей)Сумма размеров полей (с выравниванием){}
pointerСодержит адрес памяти переменной4 (на 32-битной) / 8 (на 64-битной)nil
interfaceОпределяет поведение, но не хранит данные16 (на 64-битной системе)nil

3. Указатели

Указатели хранят адреса памяти:

var p *int // создать указатель
a := 2
p = &a
*p++
fmt.Println(*p) // значение (3)
fmt.Println(&p) // адрес (0xc0000a2038)

4. Константа

Константа содержит неизменяемое значение, созданное при ее объявлении.

const Pi float64 = 3.14159
const Greeting = "Hello, Go!"

Перечисление констант:

const (
  Sunday = iota // 0
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday  // 6
  _         // пропустить 7
  New       // 8
)

5. Логические операторы

ОператорНазваниеОписание
&&И (AND)Возвращает true, если обе условия истинны
||ИЛИ (OR)Возвращает true, если хотя бы одно из условий истинно
!НЕ (NOT)Инвертирует (отрицает) булево выражение

6. Управление потоком исполнения

Ключевое словоНазначение
fallthroughПринудительно выполняет следующий case в switch, игнорируя условия.
continueПропускает текущую итерацию цикла и переходит к следующей.
breakНемедленно выходит из цикла или оператора switch.
returnЗавершает выполнение функции и может возвращать значения.
gotoПереходит к помеченному оператору (используйте с осторожностью).
deferОткладывает выполнение функции до выхода из окружающей функции.
panicВызывает ошибку и останавливает выполнение программы.
recoverПерехватывает panic, чтобы предотвратить завершение программы.

7. Операторы создания и манипуляции в Go

ФункцияНазначение
new(T)Выделяет память для типа T, возвращает указатель на T. p := new(int)
make(T, size, cap)Создаёт срезы, отображения (maps) или каналы с заданным размером и ёмкостью.
append(slice, elems...)Добавляет элементы в срез, возвращая новый срез, если ёмкость изменилась.
copy(dst, src)Копирует элементы из src в dst, возвращает количество скопированных элементов.
len(container)Возвращает длину среза, отображения, массива или строки.
cap(slice)Возвращает ёмкость среза.
delete(map, key)Удаляет ключ из отображения (map).

8. Пользовательский тип

Golang позволяет определить новый тип:

type Age int
var myAge Age = 25

9. Составные типы (коллекции и структуры)

Группируйте несколько значений с помощью массивов, срезов, карт, структур и указателей.

9.1 Массивы (фиксированный размер)

Массивы имеют фиксированную длину и хранят элементы одного типа:

var numbers [3]int = [3]int{10, 20, 30}
fmt.Println(numbers[0]) // Вывод: 10

numbers := [...]int{1, 2, 3} // Объявление без указания точного размера

9.2 Срезы (динамический размер)

Срезы более гибкие, чем массивы, и содержат:

  • емкость,
  • длину,
  • указатель на базовый массив.
var slice1 = make([]int, 4, 7) // Установка длинны и емкости
nums := []int{10, 20, 30} // создаем срез с 3 элементами
slice4 := array[:] // Конвертируем массив в срез
nums = append(nums, 40) // Добавляем значение в срез

Усечение среза:

baseArray := [8]string{"Anna", "Max", "Eva", "Leo", "Nina", "Tom", "Sophie", "Chris"} // Базовый массив

slice1 := baseArray[1:5] // с 2 по 5 элемент [Max Eva Leo Nina]
slice2 := baseArray[:3]  // с 1 по 3 элемент [Anna Max Eva]
slice3 := baseArray[4:]  // с 5 [Nina Tom Sophie Chris]

Удалить элемент из среза:

a := []int{1, 2, 3, 4, 5, 6, 7}
a = append(a[0:2], a[3:]...)
fmt.Println(a) // [1 2 4 5 6 7]

9.3 Карта или Мапа (ключ-значение)

Карта хранит значения с использованием ключа: карта — это ссылка на хэш-таблицу.

person := map[string]int{"Alice": 25, "Bob": 30}
fmt.Println(person["Alice"]) // Вывод: 25

Проверить ключ в карте:

if value, ok := m[1]; ok {
 fmt.Println(value) // Если ключ существует
}

9.4 Структуры

Позволяют группировать различные типы в один блок:

type Person struct {
  Name string
  Age int
}
p := Person{Name: "John", Age: 30}
fmt.Println(p.Name) // Вывод: John

Анонимная структура:

student := struct {
  name string
  id   int64
  age  int32
}{
  name: "Julia",
  id:   12345,
  age:  30,
}

10. Строка

Строка — это неизменяемая последовательность символов. Но строка может быть представлена ​​как []byte или []rune и модифицирована. Rune (псевдоним int32) определяет символ (1–4 байта) для поддержки UTF-8.

11. Форматирование fmt.Printf

ФорматОписание
%vЗначение в стандартном формате
%+vСтруктура с именами полей
%#vПредставление значения в синтаксисе Go
%TТип значения
%%Литерал %
%dДесятичное число
%bДвоичное число
%oВосьмеричное число
%xШестнадцатеричное число (нижний регистр)
%XШестнадцатеричное число (верхний регистр)
%cСимвольное представление
%UЮникод-формат
%fДесятичное число (по умолчанию 6 знаков)
%.2fФиксированное число знаков после запятой (2)
%eНаучная нотация (нижний регистр)
%EНаучная нотация (верхний регистр)
%gКомпактное представление float (e или f)
%GКомпактное представление float (верхний E)
%sСтрока
%qЗаключённая в кавычки строка
%xШестнадцатеричный дамп (нижний регистр)
%XШестнадцатеричный дамп (верхний регистр)
%tБулево значение
%pАдрес указателя

12. Функция

В Go параметры передаются в функцию по значению, то есть передается их копия. Изменения параметров внутри функции не влияют на исходные переменные. Включая ссылку.

Замыкание — это анонимная функция, которая запоминает переменные из окружающей ее области действия:

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    inc := counter() // Создать встраивание
    fmt.Println(inc()) // 1
    fmt.Println(inc()) // 2
    fmt.Println(inc()) // 3
}

Функция-конструктор:

func newProduct(name, category string, price float64)
 *Product {
     return &Product{name, category, price}
}

13. Интерфейсы (Динамический тип)

Интерфейсы определяют поведение без указания точных типов:

type Speaker interface {
  MetaSpeakerInterface
  Speak() string
}
type Dog struct{}
  func (d Dog) Speak() string {
  return "Woof!"
}

var animal Speaker = Dog{}
fmt.Println(animal.Speak()) // Вывод: Woof!

Определяем тип переменной:

func display(a interface{}) {
  // Using type switch
  switch a.(type) {
  case int, uint: //задаем два условия
    fmt.Printf("Type:%T -- Value:%v\n", a, a.(int))
  case string:
    fmt.Printf("Type:%T -- Value:%v\n", a, a.(string))
  case float64:
    fmt.Printf("Type:%T -- Value:%v\n", a, a.(float64))
  default:
    fmt.Println("Type not found")
  }
}

14. Ошибка

Чтобы создать ошибку, нужно реализовать интерфейс:

type error interface {
 Error() string
}

Другие способы создание ошибки:

err := errors.New("my error") // Создаем ошибку
eff := fmt.Errorf("other error")
panic("division by zero!") // Сгенерировать ошибку и выйти

15. Defer, panic и recover

Использование вместе:

func safeFunction() {
    defer func() {
        if r := recover(); r != nil { // Обработка panic
            fmt.Println("Recovered:", r)
        }
    }()

    fmt.Println("Before panic")
    panic("Unexpected error!")  // Вызываем panic
    fmt.Println("After panic")
}

16. Горутины

Горутины — это легкие потоки в Go, которые позволяют выполнять функции одновременно. В отличие от традиционных потоков, они управляются средой выполнения Go, что делает их эффективными и масштабируемыми.

16.1. Основы горутин

Вы можете запустить новую горутину, используя ключевое слово go перед вызовом функции.

Запуск функции как горутины:

package main

import (
  "fmt"
  "time"
)

func sayHello() {
  fmt.Println("Hello from goroutine!")
}

func main() {
  go sayHello() // Starts a new goroutine
  fmt.Println("Hello from main function!")

    time.Sleep(1 * time.Second) // Ожидаем завершение горутин

// Последовательность вывода может отличаться:
// Hello from main function!
// Hello from goroutine!
}

16.2. Запуск нескольких горутин

Создание горутин в цикле:

package main

import (
  "fmt"
  "time"
)

func printNumber(n int) {
  fmt.Println("Number:", n)
}

func main() {
  for i := 1; i <= 5; i++ {
  go printNumber(i) // Создаем новую горутину на каждой итерации
  }
    time.Sleep(1 * time.Second) // Предотвращаем раннее завершение работы main
}

16.3. Синхронизация с помощью sync.WaitGroup

Использование sync.WaitGroup для ожидания горутин:

package main

import (
  "fmt"
  "sync"
)

func worker(id int, wg *sync.WaitGroup) {
  fmt.Printf("Worker %d started\n", id)
  wg.Done() // Уменьшаем счетчик WaitGroup
}

func main() {
  var wg sync.WaitGroup

  for i := 1; i <= 3; i++ {
    wg.Add(1) // Увеличиваем счетчик
    go worker(i, &wg)
  }

  wg.Wait() // Ожидаем завершение горутин
  fmt.Println("All workers finished")
}

16.4. Связь между горутинами (каналами)

Создание и использование канала:

package main

import "fmt"

func sendMessage(ch chan string) {
  ch <- "Hello from Goroutine!" // Отправляем данные в канал
}

func main() {
  ch := make(chan string) // Создаем канал

  go sendMessage(ch)

  msg := <-ch // Получение данных из канала
  fmt.Println(msg)
}

16.5. Буферизованные и небуферизованные каналы

Небуферизованные каналы (по умолчанию) блокируются до тех пор, пока данные не будут получены. Буферизованные каналы позволяют хранить сообщения.

Пример буферизованного канала:

ch := make(chan int, 3) // Размер буфера 3
ch <- 1
ch <- 2
ch <- 3
fmt.Println(<-ch) // Вывод: 1

16.6. Закрытие канала

   ch := make(chan int)

go func() {
  for i := 1; i <= 5; i++ {
  ch <- i
}
  close(ch) // Закрываем канал
}()

for num := range ch {
  fmt.Println(num) // Читать пока канал не закроется
}

16.7. Оператор Select (обработка нескольких каналов)

Оператор select позволяет goroutine ожидать на нескольких каналах:

package main

import (
  "fmt"
  "time"
)

func main() {
  ch1 := make(chan string)
  ch2 := make(chan string)

  go func() {
      time.Sleep(1 * time.Second)
      ch1 <- "Message from channel 1"
  }()

  go func() {
      time.Sleep(2 * time.Second)
      ch2 <- "Message from channel 2"
  }()

  select { // Слушаем каналы
  case msg1 := <-ch1:
      fmt.Println(msg1)
  case msg2 := <-ch2:
      fmt.Println(msg2)
  case <-time.After(3 * time.Second):
      fmt.Println("Timeout!")
  }

}

16.8. Пул рабочих процессов (управление горутинами)

import (
  "fmt"
  "sync"
  "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
  for j := range jobs {
    fmt.Printf("Worker %d processing job %d\n", id, j)
    time.Sleep(time.Second)
    results <- j * 2
  }
}

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

  var wg sync.WaitGroup

  for w := 1; w <= 3; w++ {
    wg.Add(1)
    go func(id int) {
      worker(id, jobs, results)
      wg.Done()
    }(w)
  }

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

  wg.Wait()
  close(results)

  for res := range results {
    fmt.Println("Result:", res)
  }
}

16.9 Лучшие практики для горутин

  • Всегда используйте sync.WaitGroup при управлении несколькими горутинами.
  • Используйте каналы для безопасной связи вместо общей памяти.
  • Избегайте утечек памяти, гарантируя, что все горутины завершат выполнение.
  • Ограничьте горутины при обработке множества задач с использованием рабочих пулов.
  • Используйте context.Context для отмены длительно выполняющихся горутин.

17. Обработка состояний гонки с помощью sync.Mutex

Когда несколько горутин обращаются к общим данным, могут возникнуть состояния гонки. sync.Mutex (взаимное исключение) помогает предотвратить это.

package main

import (
  "fmt"
  "sync"
  "time"
)

var counter = 0
var mu sync.Mutex // Mutex для безапасного доступа

func increment() {
  for i := 0; i < 1000; i++ {
  mu.Lock() // Блокируем доступ
  counter++ // Обновляем
  mu.Unlock() // Снимаем блокировку
  }
}

func main() {
  for i := 0; i < 5; i++ {
    go increment()
  }

  time.Sleep(time.Second)
  fmt.Println("Final Counter:", counter)
}

18. Синхронизация чтения-записи с помощью sync.RWMutex

Если у вас несколько читателей, но только один писатель, используйте sync.RWMutex:

package main

import (
  "fmt"
  "sync"
  "time"
)

var value = 0
var rw sync.RWMutex // Read-Write Mutex

func readValue(id int) {
  rw.RLock() // Блокировка на чтение
  fmt.Printf("Reader %d reads value: %d\n", id, value)
  rw.RUnlock()
}

func writeValue(newValue int) {
  rw.Lock() // Блокировка на запись
  value = newValue
  fmt.Printf("Writer updated value to %d\n", value)
  rw.Unlock()
}

func main() {
  go readValue(1)
  go readValue(2)
  go writeValue(42)
  go readValue(3)

  time.Sleep(time.Second)
// Несколько читателей могут читать одновременно, но писать может только один писатель.
}

19. Отмена горутины с помощью context.Context

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

Отмена горутины:

package main

import (
  "context"
  "fmt"
  "time"
)

func worker(ctx context.Context) {
  for {
    select {
      case <-ctx.Done():
        fmt.Println("Worker stopped")
        return
      default:
        fmt.Println("Working...")
        time.Sleep(500 * time.Millisecond)
    }
  }
}

func main() {
  ctx, cancel := context.WithCancel(context.Background())

  go worker(ctx)

  time.Sleep(2 * time.Second)
  cancel() // Отправляем сигнал завершения

  time.Sleep(time.Second)
}

20. Context.WithTimeout для таймаута

Автоматическое завершение горутины по истечении времени ожидания:

package main

import (
  "context"
  "fmt"
  "time"
)

func worker(ctx context.Context) {
  for {
    select {
      case <-ctx.Done():
        fmt.Println("Timeout reached, worker stopping")
        return
      default:
        fmt.Println("Worker is running...")
        time.Sleep(500 * time.Millisecond)
    }
  }
}

func main() {
  ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  defer cancel() // Завершаем

      go worker(ctx)

      time.Sleep(3 * time.Second)

}

21. Использование sync.Once для однократной инициализации

Используйте sync.Once, чтобы гарантировать, что функция будет запущена только один раз, даже если она вызывается несколько раз:

package main

import (
 "fmt"
 "sync"
)

var once sync.Once

func initConfig() {
 fmt.Println("Initializing config...")
}

func main() {
 wg := sync.WaitGroup{}

 for i := 0; i < 3; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   once.Do(initConfig) // Запустить единожды
  }()
 }

 wg.Wait()
}

22. Использование sync.Cond для координации горутин

sync.Cond позволяет горутинам ожидать условия перед продолжением:

package main

import (
  "fmt"
  "sync"
  "time"
)

var cond = sync.NewCond(&sync.Mutex{})
var ready = false

func worker(id int) {
  cond.L.Lock()
  for !ready {
    cond.Wait() // Ожидание сигнала
  }
  fmt.Printf("Worker %d is running\n", id)
  cond.L.Unlock()
}

func main() {
  for i := 1; i <= 3; i++ {
    go worker(i)
  }

  time.Sleep(time.Second)
  cond.L.Lock()
  ready = true
  cond.Broadcast() // Уведомить ожидающее горутины
  cond.L.Unlock()

  time.Sleep(time.Second)
}

23. Лучшие практики синхронизации горутин

  • sync.WaitGroup для ожидания завершения горутин.
  • sync.Mutex или sync.RWMutex для предотвращения состояний гонки.
  • каналы для безопасной связи между горутинами.
  • context.Context для контролируемой отмены.
  • рабочие пулы для эффективного управления горутинами.
  • sync.Once для однократного выполнения.