2015-11-17 5 views
2

Это не вопрос о том, как лучше написать это. Речь идет конкретно о том, почему Go вызывает тупик в этом сценарии.Почему это вызывает тупик в Go?

package main 

import "fmt" 

func main() { 
    chan1 := make(chan bool) 
    chan2 := make(chan bool) 

    go func() { 
     for { 
      <-chan1 
      fmt.Printf("chan1\n") 
      chan2 <- true 
     } 
    }() 

    go func() { 
     for { 
      <-chan2 
      fmt.Printf("chan2\n") 
      chan1 <- true 
     } 
    }() 

    for { 
     chan1 <- true 
    } 
} 

Выходы:

chan1 
chan2 
chan1 
chan2 
chan1 
fatal error: all goroutines are asleep - deadlock! 

goroutine 1 [chan send]: 
goroutine 5 [chan send]: 
goroutine 6 [chan send]: 
exit status 2 

Почему это не вызывает бесконечный цикл? Как получилось, что два полных «пинг-пинга» (а не только один), прежде чем сдаться?

ответ

6

С точки зрения времени выполнения вы получаете тупик, потому что все подпрограммы пытаются отправить на канал, и нет никакой процедуры, ожидающей получения чего-либо.

Но Почему это происходит? Я расскажу вам историю, поскольку мне нравится визуализировать, что делают мои подпрограммы, когда я сталкиваюсь с тупиком.

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

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

Другими словами, вы ввели третьего игрока, который нарушает игру. Он должен быть арбитром, пропустившим первый мяч в начале игры, наблюдая за ним, но прекратите производство мячей! Это означает, что вместо того, чтобы иметь петлю в вашей основной процедуре, должно быть просто chan1 <- true (и некоторое условие ждать, поэтому мы не выходим из программы).

Если вы включили ведение журнала в цикле основной процедуры, вы увидите, что тупик встречается всегда на третьей итерации. Количество выполняемых других подпрограмм зависит от планировщика. Возвращение истории: первая итерация - это начало первого шара; следующая итерация - загадочный второй мяч, но это можно обработать. Третья итерация - это тупик - это воплощает в жизнь третий шар, который никто не может решить.

1
goroutine 1 [chan send]: 
goroutine 5 [chan send]: 
goroutine 6 [chan send]: 

Это говорит все это: все ваши goroutines блокированы пытается отправить на канал ни с кем не получить на другом конце.

Итак, ваши первые goroutine блокируют на chan2 <- true, ваши второстепенные блоки на chan1 <- true и ваши главные блоки goroutine сами по себе chan1 <- true.

Что касается того, почему он делает два «полных пинг-пинга», как вы говорите, это зависит от планирования и от которого отправитель <-chan1 решает получить в первую очередь.

На моем компьютере, я получаю больше, и это меняется каждый раз, когда я запустить его:

chan1 
chan2 
chan1 
chan2 
chan1 
chan2 
chan1 
chan2 
chan1 
chan2 
chan1 
chan2 
chan1 
fatal error: all goroutines are asleep - deadlock! 
+0

Это полезно и имеет смысл. Поэтому я собираюсь предположить, что theres никоим образом канал не может иметь более чем одну добавленную стоимость, поэтому это означает, что Go уже считал goroutine «спящим», а не просто заблокированным каналом. Как/почему он будет спать? –

+0

Он заблокирован операциями с каналами, что не может разблокировать. Среда выполнения обнаруживает, что все goroutines находятся в заблокированном состоянии, поэтому нет события, которое могло бы разблокировать любой из них. – HectorJ

1

Это выглядит сложным, но ответ прост.

Это будет тупик, когда:

  • Первая подпрограмма пытается написать chan2
  • Второй маршрут пытается написать chan1.
  • Main пытается написать chan1.

Как это может произойти? Пример:

  • Главная запись chan1. Блоки на другой пишут.
  • Рутина 1: chan1 получает от Main. Печать. Блоки по записи chan2.
  • Рутина 2: chan2 получает. Печать. Блоки на записи chan1.
  • Рутина 1: chan1 получает от рутины 2. Печатает. Блоки по записи chan2.
  • Рутина 2: chan2 получает. Печать. Блоки на записи chan1.
  • Основные записи chan1. Блоки на другой пишут.
  • Рутина 1: chan1 получает от Main. Печать. Блоки по записи chan2.
  • Основные записи chan1. Блоки на другой пишут.

В настоящее время все процедуры заблокированы. то есть:

Рутина 1 не может писать в chan2, потому что процедура 2 не получает, но на самом деле заблокирована, пытаясь написать chan1. Но никто не слушает chan1.

Как сказал @HectorJ, все зависит от планировщика. Но в этой установке тупик неизбежен.