Какие основные примитивы синхронизации и коммуникации горутин вы знаете?

Ответ

В Go для управления конкурентным выполнением кода используются следующие ключевые примитивы из пакета sync и встроенные возможности языка:

  1. Каналы (channels) — основной способ безопасной коммуникации и обмена данными между горутинами. Они обеспечивают синхронизацию, так как операция отправки или получения блокируется до тех пор, пока на другом конце не будет готова принимающая или отправляющая сторона.

    // Небуферизованный канал
    ch := make(chan int)
    go func() { 
        ch <- 42 // Отправка блокируется, пока кто-то не прочитает
    }()
    val := <-ch // Чтение блокируется, пока кто-то не отправит
  2. sync.WaitGroup — счётчик, который позволяет ожидать завершения выполнения группы горутин. Основные методы: Add() для увеличения счётчика, Done() для уменьшения и Wait() для блокировки до тех пор, пока счётчик не станет равен нулю.

    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1) // Увеличиваем счетчик
        go func() {
            defer wg.Done() // Уменьшаем по завершении
            // ... какая-то работа
        }()
    }
    wg.Wait() // Ожидаем, пока все горутины завершатся
  3. sync.Mutex и sync.RWMutex — мьютексы для защиты критических секций (участков кода, работающих с общими данными).

    • Mutex предоставляет эксклюзивную блокировку (только одна горутина может владеть блокировкой).
    • RWMutex позволяет либо одного "писателя", либо множество "читателей", что повышает производительность в сценариях с частым чтением и редкой записью.
    var mu sync.Mutex
    var counter int
    
    mu.Lock()   // Захватываем блокировку
    counter++
    mu.Unlock() // Освобождаем
  4. sync.Once — гарантирует, что определённый участок кода будет выполнен только один раз за всё время работы программы, независимо от количества попыток вызова. Идеально подходит для инициализации синглтонов.

    var once sync.Once
    var dbConnection *DB
    
    func GetDBConnection() *DB {
        once.Do(func() {
            dbConnection = connectToDatabase()
        })
        return dbConnection
    }
  5. select — оператор, который позволяет горутине ожидать выполнения нескольких операций с каналами одновременно. Это "switch для каналов".

    select {
    case msg1 := <-ch1:
        fmt.Println("Получено из ch1", msg1)
    case ch2 <- msg2:
        fmt.Println("Отправлено в ch2")
    case <-time.After(1 * time.Second):
        fmt.Println("Тайм-аут")
    default:
        // Выполняется, если ни один из каналов не готов
    }
  6. context.Context — механизм для передачи сигналов отмены, таймаутов и request-scoped данных между горутинами. Это стандартный способ управлять жизненным циклом операций, особенно в веб-серверах и распределённых системах.

    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel() // Важно вызывать cancel, чтобы освободить ресурсы
  7. sync.Cond — условная переменная, которая позволяет горутинам ожидать наступления определённого события или условия. Используется реже, но полезна для реализации сложных сценариев синхронизации (например, producer/consumer с общим буфером).

  8. sync.Pool — пул для временных объектов, который помогает снизить нагрузку на сборщик мусора. Позволяет переиспользовать выделенную память для объектов, которые часто создаются и уничтожаются.