在Go中使用简单频道的竞争条件?

时间:2013-12-08 09:01:12

标签: go channel

我是Go的新手,并且在使用Go版本1.2在Linux上运行的代码块非常小的情况下,我感到难以接受。

基本上,我为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 个答案:

答案 0 :(得分:6)

你应该等到你的goroutine执行,然后你的,例如,你可以用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

答案 1 :(得分:3)

您似乎期望接收goroutine在第二个fmt.Println执行之前运行完成。这不能保证是这种情况。如果程序终止,则不能保证goroutine到达其功能的末尾。

当您看到未显示“GOT IT”消息的输出时,频道发送了消息,但{goroutine之前完成了main功能。程序终止,goroutine永远不会有机会拨打fmt.Printf

在您引用的示例中,main函数以此结尾:

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

由于main功能阻止接收消息,因此在此示例中,goroutine始终运行至完成。在你的代码中,你的goroutine在从通道接收后执行一个步骤,并且未定义goroutine或main函数是否会在接收后执行下一行。

答案 2 :(得分:2)

你有两个goroutines,一个在main()(隐含的是goroutine)和匿名的一个。

他们通过同步通道进行通信,因此在通道通信之后,保证它们是同步的。

此时,main()goroutine中的代码如下所示:

fmt.Println("[-] putting num on a channel")

并且匿名goroutine中留下的代码如下所示:

fmt.Println("GOT IT: %d\n", number)

现在你正在比赛:这些Println的输出可能以任何顺序出现,甚至可能出现混合。当主要的Println()完成时,该goroutine上发生的下一件事就是你的程序将被停止。这可能会阻止匿名goroutine中的部分或全部Println出现。