Найти баг: deadlock в select
Условие
Найдите проблему deadlock в следующем коде:
func FindMaxProblem1() {
var wg sync.WaitGroup
ch := make(chan string, 5)
mu := sync.Mutex{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func(ch chan<- string, i int, grp *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
msg := fmt.Sprintf("Goroutine %s", strconv.Itoa(i))
ch <- msg
}(ch, i, &wg)
}
for {
select {
case q := <-ch:
fmt.Println(q)
}
}
wg.Wait()
}
Вопросы
- Почему возникает deadlock?
- Как исправить код?
- Нужен ли здесь мьютекс?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Найти баг — deadlock в select
Проблема 1: Бесконечный select без default
Основная проблема — бесконечный for цикл с select:
for {
select {
case q := <-ch:
fmt.Println(q)
}
}
Этот код никогда не завершится, потому что:
- Все 5 горутин выполнены и заблокированы в
mu.Lock() - Основной поток вечно ждёт данные из
ch wg.Wait()никогда не достигается
Deadlock: основной поток ждёт горутины, но горутины не могут завершиться (ждут отправки в канал, который основной поток не успевает читать), а основной поток не может закончить читать, потому что for бесконечен.
Проблема 2: Неправильная логика синхронизации
Все горутины конкурируют за один mu.Mutex, но в буферизированном канале нет необходимости синхронизировать отправку. Мьютекс создаёт дополнительные задержки и усложняет диагностику проблем.
Исправленный вариант 1: С сигналом завершения
func FindMaxFixed1() {
var wg sync.WaitGroup
ch := make(chan string, 5)
// Запускаем горутины (мьютекс не нужен)
for i := 0; i < 5; i++ {
wg.Add(1)
go func(ch chan<- string, i int) {
defer wg.Done()
msg := fmt.Sprintf("Goroutine %d", i)
ch <- msg
}(ch, i)
}
// Закрываем канал после завершения всех горутин
go func() {
wg.Wait()
close(ch)
}()
// Читаем из канала до его закрытия
for q := range ch {
fmt.Println(q)
}
}
Как работает:
- Горутины пишут в канал и завершаются
- Отдельная горутина ждёт
wg.Wait(), потом закрывает канал for range chавтоматически завершается при закрытии канала- Deadlock исчезает
Исправленный вариант 2: С контролем количества читаемых элементов
func FindMaxFixed2() {
var wg sync.WaitGroup
ch := make(chan string, 5)
for i := 0; i < 5; i++ {
wg.Add(1)
go func(ch chan<- string, i int) {
defer wg.Done()
msg := fmt.Sprintf("Goroutine %d", i)
ch <- msg
}(ch, i)
}
// Читаем ровно 5 результатов
for i := 0; i < 5; i++ {
q := <-ch
fmt.Println(q)
}
wg.Wait()
}
Как работает:
- Знаем точное количество горутин (5)
- Читаем ровно 5 элементов из канала
- После этого выходим из цикла
wg.Wait()гарантирует завершение горутин
Исправленный вариант 3: С default case в select
func FindMaxFixed3() {
var wg sync.WaitGroup
ch := make(chan string, 5)
for i := 0; i < 5; i++ {
wg.Add(1)
go func(ch chan<- string, i int) {
defer wg.Done()
msg := fmt.Sprintf("Goroutine %d", i)
ch <- msg
}(ch, i)
}
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
for {
select {
case q := <-ch:
fmt.Println(q)
case <-done:
return
}
}
}
Как работает:
- Используем отдельный канал
doneдля сигнала завершения selectполучает сигнал и выходит из функции- Более явный контроль над окончанием
Нужен ли здесь мьютекс?
Нет, по следующим причинам:
-
Канал буферизирован (
make(chan string, 5))- Горутины не блокируют друг друга при отправке
- Можно отправить до 5 сообщений без синхронизации
-
Нет общего состояния
- Каждая горутина независима
- Не изменяем общие переменные
-
Каналы — синхронизация
- Go рекомендует передавать данные по каналам, а не синхронизировать через мьютексы
- "Do not communicate by sharing memory; share memory by communicating"
-
Мьютекс замораживает все горутины
- Когда одна горутина в критической секции, остальные ждут
- Это противоречит параллелизму
Краткие ответы
1. Почему deadlock?
- Основной поток вечно ждёт в
selectна строкеq := <-ch - Горутины завершены, но
wg.Wait()никогда не выполнится - Программа зависает
2. Как исправить?
- Закрывать канал после завершения горутин (
close(ch)) - Использовать
for range chили считать элементы - Или сигнализировать завершение отдельным каналом
3. Нужен ли мьютекс?
- Нет. Буферизированный канал справляется с синхронизацией
- Мьютекс усложняет код и замораживает параллелизм
Это типичная ошибка при работе с каналами и горутинами в Go.