К каким негативным последствиям приводит неправильная синхронизация в Go?

Ответ

Неправильная синхронизация в конкурентных программах на Go приводит к трудноуловимым ошибкам и проблемам с производительностью. Основные последствия:

  1. Состояние гонки (Race Condition). Возникает, когда несколько горутин одновременно обращаются к одной и той же области памяти, и хотя бы одна из них выполняет запись. Результат операции становится непредсказуемым и зависит от порядка выполнения горутин, что приводит к повреждению данных.

    var counter int
    // Запускаем две горутины, инкрементирующие счетчик
    go func() { counter++ }()
    go func() { counter++ }()
    // Финальное значение counter может быть 1 или 2

    Диагностика: go run -race или go test -race.

  2. Взаимоблокировка (Deadlock). Ситуация, когда две или более горутины бесконечно ожидают друг друга, блокируя ресурсы, необходимые другим. Программа полностью "зависает".

    var muA, muB sync.Mutex
    
    // Горутина 1: A -> B
    go func() {
        muA.Lock()
        time.Sleep(time.Millisecond)
        muB.Lock() // Ждет, пока Горутина 2 освободит muB
        // ...
        muA.Unlock()
        muB.Unlock()
    }()
    
    // Горутина 2: B -> A
    go func() {
        muB.Lock()
        time.Sleep(time.Millisecond)
        muA.Lock() // Ждет, пока Горутина 1 освободит muA
        // ...
        muB.Unlock()
        muA.Unlock()
    }() // Результат - deadlock
  3. Живая блокировка (Livelock). Горутины постоянно изменяют свое состояние в ответ на действия друг друга, но не выполняют полезной работы. В отличие от deadlock, они активны, но "топчутся на месте".

  4. Голодание (Starvation). Одна или несколько горутин не могут получить доступ к ресурсу, потому что он постоянно занят другими, более "жадными" или высокоприоритетными горутинами.

  5. Утечки горутин. Как следствие неправильной синхронизации (например, ожидание данных из канала, в который никто не пишет), горутины могут остаться заблокированными навсегда, потребляя память.