Ответ
Deadlock (взаимная блокировка) — это ситуация в конкурентном программировании, когда две или более горутины находятся в состоянии бесконечного ожидания, потому что каждая из них ожидает освобождения ресурса, который захвачен другой горутиной из этой же группы.
Классический пример с мьютексами:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var mu1, mu2 sync.Mutex
var wg sync.WaitGroup
wg.Add(2)
// Горутина 1: захватывает mu1, затем пытается захватить mu2
go func() {
defer wg.Done()
fmt.Println("Горутина 1: захватывает mu1...")
mu1.Lock()
defer mu1.Unlock()
time.Sleep(100 * time.Millisecond)
fmt.Println("Горутина 1: пытается захватить mu2...")
mu2.Lock()
defer mu2.Unlock()
}()
// Горутина 2: захватывает mu2, затем пытается захватить mu1
go func() {
defer wg.Done()
fmt.Println("Горутина 2: захватывает mu2...")
mu2.Lock()
defer mu2.Unlock()
time.Sleep(100 * time.Millisecond)
fmt.Println("Горутина 2: пытается захватить mu1...")
mu1.Lock()
defer mu1.Unlock()
}()
wg.Wait() // Эта строка никогда не будет достигнута
fmt.Println("Программа завершена")
}
В этом примере Горутина 1
захватывает mu1
и ждёт mu2
, в то время как Горутина 2
захватывает mu2
и ждёт mu1
. Программа зависает.
Как избежать Deadlock:
Порядок блокировок: Самый надежный способ — захватывать мьютексы всегда в одном и том же строгом порядке. Если бы обе горутины сначала пытались захватить
mu1
, а затемmu2
, взаимной блокировки бы не произошло.Использование
sync.RWMutex
: Если одна из операций только читает данные, можно использовать блокировку на чтение (RLock
), которая не конфликтует с другими блокировками на чтение.Таймауты: Использовать
sync.Mutex.TryLock()
(в Go 1.18+) или реализовать блокировку с таймаутом, чтобы горутина не ждала ресурс вечно.Избегать вложенных блокировок: По возможности, старайтесь не захватывать один мьютекс, пока удерживаете другой.