Состояние гонки с простым каналом в 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)

Теперь вы участвуете в гонках: выход из этих Printlns могут появляться в любом порядке или даже смешиваться. Когда Println() Следующее, что произойдёт в этой процедуре после главной обработки, - это то, что ваша программа будет остановлена. Это может помешать некоторым или всем Println от анонимного goroutine от появления.

Вы, кажется, ожидаете, что принимающая программа будет завершена до второго fmt.Println выполняет. Это не обязательно так. Если программа завершается, goroutines не гарантируется, что они достигнут конца своих функций.

Когда вы видите вывод, который не отображает сообщение "GOT IT", канал доставил свое сообщение, но main Функция завершена до того, как сделал Goroutine. Программа прервана, и у программы нет возможности позвонить fmt.Printf

В приведенном вами примере main Функция заканчивается этим:

go func() { messages <- "ping" }()
msg := <-messages
fmt.Println(msg)

Так как main Функциональные блоки, пока не получит сообщение, в этом примере программа всегда выполняется до завершения. В вашем коде ваша подпрограмма выполняет шаг после получения от канала, и не определено, будет ли подпрограмма или основная функция выполнять следующую строку после получения.

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