Назови основные проблемы конкурентности в Go и способы их решения.

Ответ

При написании конкурентного кода в Go можно столкнуться с несколькими классическими проблемами. Вот основные из них:

1. Гонка данных (Data Race)

  • Проблема: Возникает, когда две или более горутины одновременно обращаются к одной и той же области памяти, и хотя бы одна из них выполняет запись. Результат операции становится непредсказуемым.
  • Пример:
    var counter int
    // Две горутины инкрементируют счетчик без синхронизации
    go func() { counter++ }()
    go func() { counter++ }()
    // Финальное значение counter может быть 1 или 2
  • Решение: Использовать примитивы синхронизации для защиты общих данных:
    • sync.Mutex или sync.RWMutex для эксклюзивного доступа.
    • Каналы (channels) для безопасной передачи данных между горутинами.
    • Атомарные операции из пакета sync/atomic.
  • Диагностика: Использовать встроенный детектор гонок: go run -race main.go.

2. Взаимная блокировка (Deadlock)

  • Проблема: Ситуация, когда две или более горутины бесконечно ожидают освобождения ресурсов, захваченных друг другом. Программа "зависает".
  • Пример (неправильный порядок захвата мьютексов):
    var mu1, mu2 sync.Mutex
    go func() { // Горутина 1
        mu1.Lock()
        time.Sleep(10 * time.Millisecond)
        mu2.Lock() // Ждет, пока Горутина 2 освободит mu2
        // ...
        mu1.Unlock()
        mu2.Unlock()
    }()
    go func() { // Горутина 2
        mu2.Lock()
        time.Sleep(10 * time.Millisecond)
        mu1.Lock() // Ждет, пока Горутина 1 освободит mu1
        // ...
        mu2.Unlock()
        mu1.Unlock()
    }()
  • Решение: Вводить строгий порядок захвата блокировок. Все горутины должны захватывать мьютексы в одной и той же последовательности (например, всегда сначала mu1, потом mu2).

3. Голодание (Starvation)

  • Проблема: Одна или несколько горутин не могут получить доступ к необходимому ресурсу и не могут продолжить свою работу, в то время как другие горутины постоянно этот ресурс используют.
  • Пример: Может возникнуть при неправильном использовании мьютексов, когда одна "жадная" горутина постоянно захватывает и освобождает мьютекс в цикле, не давая другим горутинам шанса его захватить.
  • Решение: Справедливое распределение доступа к ресурсам. В Go sync.Mutex не гарантирует справедливость, но на практике голодание встречается редко. Использование каналов для управления доступом может помочь спроектировать более справедливую систему.