2016-05-13 2 views
4

Вот моя программа, которая производит тупик, как я могу избежать этого и то, что рекомендуется, чтобы справиться с такой ситуацией.Как избежать тупиковой ситуации в этой программе golang?

Проблема после таймаута, как определить, что на моем канале нет читателя?

var wg sync.WaitGroup 

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    wg.Wait() 
} 

func readFromChannel(c chan int, ti <-chan time.Time) { 
    select { 
    case x := <-c: 
     fmt.Println("Read", x) 
    case <-ti: 
     fmt.Println("TIMED OUT") 
    } 
    wg.Done() 
} 

ответ

1

У вас есть небуферизованный канал. Согласно docs:

Если канал небуферизован, отправитель блоков, пока приемник имеет получил значение. Если канал имеет буфер, отправитель блоки только до тех пор, пока значение было скопировано в буфер

Изменяя канал для буферизации, мы можем избежать тупика.

c := make(chan int, 10) // holds 10 ints 

Я также предлагаю читать https://golang.org/doc/effective_go.html#channels, у него есть некоторые хорошие вещи там, связанные с каналами.

6

Итак, давайте посмотрим, что действительно происходит в вашем источнике. У вас есть два goroutines (их более двух, но мы сосредоточимся на явных), main и readFromChannel.

Давайте посмотрим на то, что readFromChannel делает:

if channel `c` is not empty before `ti` has expired, print its contents and return, after signalling its completion to wait group. 
if `ti` has expired before `c` is not empty, print "TIMED OUT" and return, after signalling its completion to wait group. 

сейчас Main:

adds to waitgroup 
make a channel `c` 
start a goroutine `readFromChannel` 
sleep for 5 seconds 
send 10 to channel `c` 
call wait for waitgroup 

Теперь, давайте идти через поток исполнения для кода, одновременно (ваш код может/не может выполнить в этом порядке каждый раз, помните об этом)

1) wg.Add(1) 
2) c := make(chan int) 
3) go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
#timer ti starts# 
4) time.Sleep(time.Duration(5) * time.Second) 
#MAIN Goroutine begins sleep 
#timer ti expires# 
5) case <-ti: 
6) fmt.Println("TIMED OUT") 
7) wg.Done() 
# readFromChannel Goroutine returns # 
#MAIN Goroutine exits sleep# 
8) c<-10 
9) ......#DEADLOCK# 

Теперь вы можете указать почему вы зашли в тупик. В режиме небуферизованных каналов будет блокировать, пока что-то не произойдет на другом конце канала, независимо от того, отправляете или получаете ли вы. Таким образом, c <- 10 будет блокироваться, пока что-то не прочитает с другого конца c, но у вас был горутин, который у вас для этого выпал 2 секунды назад. Поэтому c блоки навсегда, а с main - последний горутин слева, вы получаете Тупик.

Как это предотвратить? При использовании каналов убедитесь, что на другом конце канала всегда есть receive для каждого send. Вы также можете использовать буферный канал, но в вашем коде выше это не будет «правильным» решением.

Вот мое исправление тупика:

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    wg.Wait() 
} 

func readFromChannel(c chan int, ti <-chan time.Time) { 
     // the forloop will run forever 
    loop: // ** 
    for { 
     select { 
      case x := <-c: 
        fmt.Println("Read", x) 
        break loop // breaks out of the for loop and the select ** 
      case <-ti: 
        fmt.Println("TIMED OUT") 
      } 
    } 
    wg.Done() 
} 

** see this answer for details

+0

Это один из самых полезных ответов, которые я читал в то время. Просто чтобы убедиться, что я следую: «исправление» работает, потому что он удерживает канал приемника даже после таймаута. Итак, 'wg.Done()' (и прекращение процедуры 'main' go) будет когда-либо происходить, если что-то читалось с' c', правильно? –

+1

Правильно, но чтобы немного проясниться, он держит ** goroutine ** включенным. – AJPennster

0

Ваша проблема заключается в том, что вы используете select заявление, но вы не используете внутри goroutine.

go func() { 
    for { 
     select { 
     case x := <-c: 
      fmt.Println("Read", x) 
     case <-ti: 
      fmt.Println("TIMED OUT") 
     } 
    } 
}() 

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

Использование операции отправки в операторе select со случаем по умолчанию гарантирует, что отправка будет неблокирующей! Если случаев нет, выбор блокирует выполнение навсегда.

https://play.golang.org/p/Ai1ggveb4s

0

Это старый вопрос, но я ныряя глубоко в изучение каналов себя и нашел это здесь.

Думаю, вам просто нужно закрыть канал после того, как вы отправили его на него?

Код:

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    close(c) // <- CLOSE IT HERE 
    wg.Wait() 
}