Состояние гонки с простым каналом в Go?
Я новичок в Go и нахожусь в тупике из-за довольно редкого состояния гонки с очень маленьким блоком кода, работающего на Linux с Go версии 1.2.
По сути, я создаю канал для int
, запустите процедуру go для чтения из канала, а затем запишите в него один int.
package main
import "fmt"
func main() {
channel := make(chan int)
go func() {
number := <- channel
fmt.Printf("GOT IT: %d\n", number)
}()
fmt.Println("[+] putting num on channel")
channel <- 42
fmt.Println("[-] putting num on channel")
}
Выход около 90% времени, как и ожидалось:
$ go run test.go
[+] putting num on channel
GOT IT: 42
[-] putting num on channel
Однако примерно в 10% случаев подпрограмма go просто не читает число с канала и ничего не печатает:
$ go run test.go
[+] putting num on channel
[-] putting num on channel
Я озадачен, потому что этот код очень похож на пример по адресу https://gobyexample.com/channels(с которым у меня нет этой проблемы), за исключением того, что я читаю из канала в своей подпрограмме go вместо записи на канал.
У меня есть фундаментальное недопонимание того, как работают каналы, или здесь есть что-то еще?
3 ответа
Вы должны подождать, пока ваша программа будет выполнена, а затем, например, вы можете сделать это с sync.WaitGroup
:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
channel := make(chan int)
wg.Add(1)
go func() {
number := <-channel
fmt.Printf("GOT IT: %d\n", number)
wg.Done()
}()
fmt.Println("[+] putting num on channel")
channel <- 42
wg.Wait()
fmt.Println("[-] putting num on channel")
}
(goplay: http://play.golang.org/p/VycxTw_4vu)
Также вы можете сделать это с "каналом уведомлений", который указывает, что работа выполнена:
package main
import "fmt"
func main() {
channel := make(chan int)
done := make(chan bool)
go func() {
number := <-channel
fmt.Printf("GOT IT: %d\n", number)
done <- true
}()
fmt.Println("[+] putting num on channel")
channel <- 42
<-done
fmt.Println("[-] putting num on channel")
}
(goplay: http://play.golang.org/p/fApWQgtr4D)
У вас есть две подпрограммы, одна в main() (которая неявно является подпрограммой) и анонимная.
Они общаются по синхронному каналу, поэтому после связи по каналу они гарантированно синхронизируются.
На этом этапе код, оставленный в функции main (), выглядит следующим образом:
fmt.Println("[-] putting num on a channel")
и код, оставленный в анонимной программе, выглядит так:
fmt.Println("GOT IT: %d\n", number)
Теперь вы участвуете в гонках: выход из этих Println
s могут появляться в любом порядке или даже смешиваться. Когда Println()
Следующее, что произойдёт в этой процедуре после главной обработки, - это то, что ваша программа будет остановлена. Это может помешать некоторым или всем Println
от анонимного goroutine от появления.
Вы, кажется, ожидаете, что принимающая программа будет завершена до второго fmt.Println
выполняет. Это не обязательно так. Если программа завершается, goroutines не гарантируется, что они достигнут конца своих функций.
Когда вы видите вывод, который не отображает сообщение "GOT IT", канал доставил свое сообщение, но main
Функция завершена до того, как сделал Goroutine. Программа прервана, и у программы нет возможности позвонить fmt.Printf
В приведенном вами примере main
Функция заканчивается этим:
go func() { messages <- "ping" }()
msg := <-messages
fmt.Println(msg)
Так как main
Функциональные блоки, пока не получит сообщение, в этом примере программа всегда выполняется до завершения. В вашем коде ваша подпрограмма выполняет шаг после получения от канала, и не определено, будет ли подпрограмма или основная функция выполнять следующую строку после получения.