Поведение Go Channels выглядит противоречивым

Я вижу несоответствие в том, как работают небуферизованные каналы - это либо несоответствие в Go, либо в моем понимании Go...

Вот простой пример с выводом. "Несоответствие" связано со строками "make channel".

package main
import (
    "fmt"
    )

func send(sendto chan string) {
    fmt.Println("send 1")
    sendto <- "Hello"
    fmt.Println("send 2")
    sendto <- "World"
    fmt.Println("send 3")
    sendto <- ""
    fmt.Println("send() exit")
}

func main() {
    //hole := make(chan string)
    //hole := make(chan string, 0)
    hole := make(chan string, 1)
    go send(hole)
    fmt.Println("main loop")
    carryon := true
    for carryon {
        msg := <- hole
        if msg == "" {
            carryon = false
        } else {
            fmt.Println(" recd ", msg)
        }
    }
}

Когда я запускаю, как указано выше, вывод соответствует ожидаемому (а также ожидаемому для размера буфера 2). т. е. канал имеет буфер 1, который содержит одно значение - при следующей попытке записи происходит переключение контекста на основной, чтобы позволить ему использовать первое значение.

main loop
send 1
send 2
 recd  Hello
send 3
 recd  World
send() exit

Когда я тогда изменю строку make channel на:

hole := make(chan string, 0)

Выход:

main loop
send 1
send 2
 recd  Hello
 recd  World
send 3
send() exit

Я бы ожидал send 2 и recd Hello быть наоборот...

Я получаю такой же вывод для hole := make(chan string)

Я проверил спецификацию и там написано

Емкость, в количестве элементов, устанавливает размер буфера в канале. Если пропускная способность равна нулю или отсутствует, канал не буферизуется, и связь устанавливается только тогда, когда отправитель и получатель готовы. В противном случае канал буферизуется, и связь успешно завершается без блокировки, если буфер не заполнен (отправляет) или не пуст (получает).

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

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

Спасибо

3 ответа

Решение

Грубо говоря: отправка и получение происходят одновременно. Детали объясняются в модели памяти Go, которая определяет это поведение. Параллельный код сложен...

Этот график для двух goroutines показывает, что происходит:

send()                  main()

fmt.Println("send 1")
sendto <- "Hello"       msg := <- hole              // sender and receiver both ready
fmt.Println("send 2")
                        fmt.Println(" recd ", msg)  // msg is "Hello"
sendto <- "World"       msg := <- hole              // sender and receiver both ready
                        fmt.Println(" recd ", msg)  // msg is "World"
fmt.Println("send 3")
sendto <- ""
fmt.Println("send() exit")

send 2 печатается раньше recd Hello потому что send() запускается в операторе print до того, как среда выполнения планирует main() выполнить снова.

Там не происходит до отношения для печати двух сообщений. Они могут быть напечатаны в любом порядке.

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

Ключевым моментом является то, что для этого не требуется, чтобы получатель немедленно приступил к обработке полученного сообщения. В частности, в вашем случае он готов, поэтому он получает значение без вызова планировщика (без переключения контекста). Программа продолжает работать до тех пор, пока не попытается отправить снова, после чего получатель не будет готов, поэтому вызывается планировщик и т. Д.

Другие вопросы по тегам