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

Я уверен, что у этой тривиальной ситуации есть простое объяснение, но я новичок в модели параллелизма 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)
}

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

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


person Salah Eddine Taouririt    schedule 06.09.2013    source источник
comment
Что происходит, когда вы звоните на свой номер телефона с того же номера телефона? Сигнал «тупик» или «занято» и т. Д. Здесь то же самое.   -  person Inanc Gumus    schedule 15.11.2019


Ответы (4)


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

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

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

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

Эта линия

c <- 1

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

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

c := make(chan int, 1) 

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

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

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

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

person Denys Séguret    schedule 06.09.2013
comment
Мне сложно думать, есть три ситуации: 1) небуферизованный канал без новой горутины - ›тупик, 2) буферный канал без новой горутины -› без тупика 3) небуферизованный канал с новой горутиной - ›run. - person Salah Eddine Taouririt; 06.09.2013
comment
Когда канал заполнен, должна быть горутина, которая принимает то, что отправляет, иначе отправитель блокируется, пока он не придет. Вы можете рассматривать небуферизованный канал как всегда полный канал. - person Denys Séguret; 06.09.2013
comment
Попробуйте представить канал как физическую трубу: если он слишком короткий или заполнен, вы должны подождать, пока кто-то на другом конце что-то возьмет, иначе, если что-то поместить внутрь, часть контента упадет на землю. - person Denys Séguret; 06.09.2013
comment
Еще одна полезная аналогия для горутин - сравнить их с логическими вентилями в аппаратной схеме, где каналы являются проводами. В примере в вопросе «ворота» были неправильно подключены к себе таким образом, чтобы они могли отправлять или получать, но не то и другое одновременно. - person Rick-777; 07.09.2013
comment
Дело не в том, что небуферизованные каналы всегда полны. Напротив, они совсем не полные. Каналы подобны кабелям между горутинами. У них нет буфера. Только входные и выходные точки каналов могут иметь сегменты, называемые буферами. - person Inanc Gumus; 15.11.2019

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

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

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

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

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

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

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
}
person bharatj    schedule 17.12.2016
comment
Это неверно: В небуферизованном канале запись в канал не произойдет до тех пор, пока не появится какой-то приемник, который ожидает получения данных. Произойдет запись в канал, и когда появится приемник, он получит данные, если нет, то возникнет тупик или утечка и т. Д. - person Inanc Gumus; 21.06.2018
comment
@InancGumus, в третьем фрагменте кода выше получатель появляется позже, но тупиковые ситуации: play.golang.org/p / HCP5KJ2aW_- - person Hem; 14.11.2019

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

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

package main

import "fmt"

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

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

fatal error: all goroutines are asleep - deadlock!

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

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

В случае №1 go выдаст ошибку с сообщением выше, так как теперь go ЗНАЕТ, что горутина никак не может получить канал и он ему нужен.

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

person Chun Yang    schedule 26.12.2017

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

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

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

person Awesome Infinity    schedule 06.06.2019