Подтвердить что ты не робот

Почему использование небуферизованного канала в той же самой процедуре приводит к тупику?

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

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

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)
}

Снова, почему?

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

4b9b3361

Ответ 1

Из документации:

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

Сказано иначе:

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

Эта строка

c <- 1

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

Вы можете сделать это не блокируя, изменив создание канала на

c := make(chan int, 1) 

чтобы в нем не было места для одного элемента в канале.

Но это не то, о чем concurrency. Как правило, вы не будете использовать канал без других goroutines, чтобы обрабатывать то, что вы вкладываете внутрь. Вы можете определить получающую горуту:

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

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

Ответ 2

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

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
}

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

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

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)
}

Это приведет к тупиковой ситуации

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

ПРИМЕЧАНИЕ. Эта же концепция применяется к буферному каналу, но отправитель не блокируется до тех пор, пока буфер не будет заполнен, что означает, что приемник не обязательно должен быть синхронизирован с каждой операцией записи.

Итак, если у нас есть буферный канал размером 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
}

Ответ 3

В этом ответе я попытаюсь объяснить сообщение об ошибке, через которое мы можем немного заглянуть в то, как 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 (BTW эта ошибка во время выполнения, а не время компиляции). Когда go запускает эту строку c <- 1, она хочет убедиться, что сообщение в канале будет получено где-нибудь (т.е. <-c). Go не знает, будет ли канал получен или нет в этот момент. Итак, дождитесь завершения работы горутинов до тех пор, пока не произойдет одно из следующих событий:

  • все горуты завершены (спящие)
  • один из goroutine пытается получить канал

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

В случае №2 программа продолжит работу, так как теперь вы ЗНАЕТЕ, что этот канал получен. Это объясняет успешный пример в примере OP.

Ответ 4

  • Буферизация удаляет синхронизацию.
  • Буферизация делает их больше похожими на почтовые ящики Эрланга.
  • Буферизованные каналы могут быть важны для некоторых проблем, но они более тонки, чтобы рассуждать о
  • По умолчанию каналы не буферизованы, это означает, что они будут принимать только посылки
    (chan <-), если есть соответствующий прием (<- chan), готовый принять отправленное значение.
  • Буферизованные каналы принимают ограниченное количество значений без соответствующего приемника для этих значений.

messages: = make (chan string, 2)/ / - канал буферизации строк до 2 значений.

Основные отправляет и получает по каналам блокировки. Тем не менее, мы можем использовать select с предложением по default для реализации неблокирующих посылок, приёмов и даже неблокирующих многопользовательских select.