Почему использование unbuffered канала в том же goroutine дает тупик

Я уверен, что есть простое объяснение в этой тривиальной ситуации, но я новичок в go модель параллелизма.

когда я запускаю этот пример

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}

Я получаю эту ошибку :

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /home/tarrsalah/src/go/src/github.com/tarrsalah/tour.golang.org/65.go:8 +0x52
exit status 2

почему ?


упаковка c <- на goroutine делает примеру, работать, как мы ожидали

package main

import "fmt"

func main() {
    c := make(chan int)        
    go func(){
       c <- 1
    }()
    fmt.Println(<-c)
}

опять же, почему ?

пожалуйста, мне нужно глубокое объяснение, а не только как устранить тупик и исправить код.

3 ответов


С документация :

Если канал не буферизован, отправитель блокирует, пока получатель не получит значение. Если канал имеет буфер, отправитель блокирует только до значения скопировано в буфер; если буфер заполнен, это означает ожидание, пока какой-либо получатель не получит значение.

сказал иначе :

  • когда канал заполнен, отправитель ждет другого goroutine для освободите место, получив
  • вы можете видеть небуферизованный канал Как всегда полный: должен быть другой goroutine, чтобы взять то, что посылает отправитель.

эта строка

c <- 1

блоки, потому что канал не буферизован. Поскольку нет другого goroutine для получения значения, ситуация не может решить, это тупик.

вы можете сделать его не блокирующим, изменив создание канала на

c := make(chan int, 1) 

Так что там место для одного предмета в канале, прежде чем он блокируется.

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

func main() {
    c := make(chan int)    
    go func() {
        fmt.Println("received:", <-c)
    }()
    c <- 1   
}

демонстрация


в unbuffered канале запись в канал не произойдет, пока не должен быть какой-то приемник, который ждет получения данных, что означает в приведенном ниже примере

func main(){
    ch := make(chan int)
    ch <- 10   /* Main routine is Blocked, because there is no routine to receive the value   */
    <- ch
}

теперь, если у нас есть другая процедура go, применяется тот же принцип

func main(){
  ch :=make(chan int)
  go task(ch)
  ch <-10
}
func task(ch chan int){
   <- ch
}

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

чтобы сделать его более ясным, давайте поменяем порядок второй и третий операторы в main функции.

func main(){
  ch := make(chan int)
  ch <- 10       /*Blocked: No routine is waiting for the data to be consumed from the channel */
  go task(ch)
}

это приведет к тупику

короче говоря, запись в unbuffered channel происходит только тогда, когда есть какая-то процедура, ожидающая чтения с канала, иначе операция записи блокируется навсегда и приводит к тупику.

Примечание: та же концепция применяется к буферному каналу, но отправитель не блокируется, пока буфер не заполнен, что означает, что приемник не обязательно должен быть синхронизирован с каждой операцией записи.

Итак, если у нас есть буферизованный канал размера 1, то ваш вышеупомянутый код будет работать

func main(){
  ch := make(chan int, 1) /*channel of size 1 */
  ch <-10  /* Not blocked: can put the value in channel buffer */
  <- ch 
}

но если мы напишем больше значений в приведенном выше примере, то произойдет взаимоблокировка

func main(){
  ch := make(chan int, 1) /*channel Buffer size 1 */
  ch <- 10
  ch <- 20 /*Blocked: Because Buffer size is already full and no one is waiting to recieve the Data  from channel */
  <- ch
  <- ch
}

в этом ответе я попытаюсь объяснить сообщение об ошибке, через которое мы можем заглянуть немного в то, как go работает с точки зрения каналов и goroutines

первый пример:

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}

сообщение об ошибке:

fatal error: all goroutines are asleep - deadlock!

в коде вообще нет goroutines (кстати, эта ошибка во время выполнения, а не во время компиляции). Когда go запускает эту строку c <- 1, Он хочет убедиться, что сообщение в канале будет получено где-то (i.e <-c). Go не знает, будет ли канал принят или нет в этот момент. Так что go будет ждать запуска goroutines, чтобы закончить, пока не произойдет одно из следующих событий:

  1. все горутины закончены(спит)
  2. один из goroutine пытается получить канал

в случае #1, go будет ошибка с сообщением выше, так как теперь go знает, что нет никакого способа, которым goroutine получит канал, и ему нужно один.

в случае #2 программа будет продолжена, так как теперь go знает, что этот канал получен. Это объясняет успешный случай в Примере OP.