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