Я пытаюсь понять, как работают каналы в golang. Код у меня очень простой, но результат удивителен.
Как указано в документации: чтение и запись из/в канал блокирует текущую горутину, поэтому я думал, что запись в канал будет блокировать канал до тех пор, пока основная процедура не даст результатов.
package main
func rtn(messages chan<- string) {
defer close(messages)
println("p1")
messages <- "ping1"
//for i := 0; i < 10000000; i++ { }
println("p2")
messages <- "ping2"
}
func main() {
messages := make(chan string)
go rtn(messages)
for msg := range messages {
println(msg)
}
}
Я думал, что это будет печатать
p1
ping1
p2
ping2
а на самом деле печатает
p1
p2
ping1
ping2
Большинство ожиданий относительно порядка параллельных операций необоснованны.
Вы используете небуферизованный канал, который работает как точка синхронизации между основной и второй горутинами.
В этом случае вы знаете только то, что когда вторая горутина здесь messages <- "ping1"
, основная находится на линии for msg := range messages
. Таким образом, нет гарантии, что основной цикл сразу достигнет println(msg)
. То есть за это время вторая горутина могла двигаться дальше и достигать строк println("p2")
и messages <- "ping2"
.
В качестве контрпримера я добавляю канал только для обеспечения полной синхронизации между отпечатками.
package main
func rtn(messages chan<- string, syncChan chan struct{}) {
defer close(messages)
println("p1")
messages <- "ping1"
//Wait for main goroutine to print its message
<-syncChan
//for i := 0; i < 10000000; i++ { }
println("p2")
messages <- "ping2"
//Wait for main goroutine to print its message
<-syncChan
}
func main() {
messages := make(chan string)
syncChan := make(chan struct{})
go rtn(messages, syncChan)
for msg := range messages {
println(msg)
//Notify the second goroutine that is free to go
syncChan <- struct{}{}
}
}
Который печатает ожидаемый результат:
p1
ping1
p2
ping2
Вот еще один пример, который дает результат, который вы ищете. В этом случае основная горутина принудительно блокируется time.Sleep()
. Это сделает вторую горутину готовой к отправке до того, как получатель будет готов к приему. Поэтому отправитель фактически заблокирует операцию отправки.
package main
import (
"time"
)
func rtn(messages chan<- string) {
defer close(messages)
println("p1")
messages <- "ping1"
//for i := 0; i < 10000000; i++ { }
println("p2")
messages <- "ping2"
}
func main() {
messages := make(chan string)
go rtn(messages)
//Put main goroutine to sleep. This will make the
//sender goroutine ready before the receiver.
//Therefore it will have to actually block!
time.Sleep(time.Millisecond * 500)
for msg := range messages {
println(msg)
}
}
поправьте меня, если я ошибаюсь, вы говорите, что вторая процедура на самом деле работает между for msg := range messages
и println(msg)
? Разве это не должно быть заблокировано?
Почему он должен быть заблокирован? Операция блокировки — это только for msg := range messages
. Более того, для таких случаев есть оптимизации, которые делают каналы. То есть, когда есть горутина, заблокированная для чтения канала, и другая горутина отправляет новое сообщение, которое разблокирует первое, тогда отправитель напрямую копирует значение в заблокированную горутину. На самом деле не помещая сообщение в очередь канала (которая даже не существует в случае небуферизованных каналов). Таким образом, горутину отправителя не нужно блокировать.
прежде чем идти дальше, спасибо, что нашли время, чтобы объяснить эти вещи. Я получил эту идею из [gobyexample.com/каналы], где было сказано По умолчанию отправка и получение блокируются до тех пор, пока отправитель и получатель не будут готовы. , поэтому я подумал, что как только я напишу в канал, основная процедура подхватит его и будет продолжать работать, пока что-то не заблокируется (а затем подхватит второй)
Я добавил новый пример, чтобы прояснить концепцию. В принципе, вышеприведенное утверждение верно. Но в вашем коде основная горутина сразу готова к приему из канала. Поэтому отправитель не заблокируется, потому что ему нечего ждать.
Все работает так, как ожидалось. Ознакомьтесь с параллелизмом Golang. (например, здесь)