在Go语言中,使用简单通道可能会出现竞态条件(Race condition)。

huangapple go评论81阅读模式
英文:

Race condition with a simple channel in Go?

问题

我是一个中文翻译助手,以下是你提供的代码的翻译:

我刚开始学习Go语言,在运行Go版本1.2的Linux系统上遇到了一个似乎比较罕见的竞态条件问题。

基本上,我创建了一个用于存放int类型的通道,启动一个goroutine来从通道中读取数据,然后向通道中写入一个整数。

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")
}

大部分情况下,输出结果是符合预期的:

$ go run test.go 
[+] putting num on channel
GOT IT: 42
[-] putting num on channel

然而,大约有10%的情况下,goroutine根本没有从通道中读取数字,也没有打印任何内容:

$ go run test.go 
[+] putting num on channel
[-] putting num on channel

我感到困惑的原因是,这段代码与https://gobyexample.com/channels中的示例非常相似(我在那个示例中没有遇到这个问题),只是我在goroutine中读取通道而不是写入通道。

我是否对通道的工作原理有基本的误解,或者还有其他什么问题?

英文:

I'm new to Go and am stumped on what appears to be a somewhat-rare race condition with a very small block of code running on Linux with Go version 1.2.

Basically, I create a channel for an int, start a go routine to read from the channel, and then write a single int to the channel.

package main

import &quot;fmt&quot;

func main() {
    channel := make(chan int)

    go func() {
        number := &lt;- channel
        fmt.Printf(&quot;GOT IT: %d\n&quot;, number)
    }()

    fmt.Println(&quot;[+] putting num on channel&quot;)
    channel &lt;- 42
    fmt.Println(&quot;[-] putting num on channel&quot;)
}

The output about 90% of the time is as expected:

$ go run test.go 
[+] putting num on channel
GOT IT: 42
[-] putting num on channel

However, about 10% of the time, the go routine simply does not read the number from the channel and prints nothing:

$ go run test.go 
[+] putting num on channel
[-] putting num on channel

I'm puzzled because this code is very similar to the example at https://gobyexample.com/channels, (which I do not have this problem with) except that I'm reading from the channel in my go routine instead of writing to the channel.

Do I have a fundamental misunderstanding of how channels work or is there something else at play here?

答案1

得分: 8

你应该等待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")
}

你也可以使用一个“通知通道”来实现,表示任务已完成:

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")
}
英文:

You should wait until your goroutine executes, and then your, for example, you can do it with sync.WaitGroup:

package main

import (
  &quot;fmt&quot;
  &quot;sync&quot;
)

func main() {
  var wg sync.WaitGroup

  channel := make(chan int)
  wg.Add(1)

  go func() {
    number := &lt;-channel
    fmt.Printf(&quot;GOT IT: %d\n&quot;, number)
    wg.Done()
  }()

  fmt.Println(&quot;[+] putting num on channel&quot;)
  channel &lt;- 42
  wg.Wait()
  fmt.Println(&quot;[-] putting num on channel&quot;)
}

(goplay: http://play.golang.org/p/VycxTw_4vu)

Also you can do it with a "notification channel", that indicates that job is done:

package main

import &quot;fmt&quot;

func main() {
  channel := make(chan int)
  done := make(chan bool)

  go func() {
    number := &lt;-channel
    fmt.Printf(&quot;GOT IT: %d\n&quot;, number)
    done &lt;- true
  }()

  fmt.Println(&quot;[+] putting num on channel&quot;)
  channel &lt;- 42
  &lt;-done
  fmt.Println(&quot;[-] putting num on channel&quot;)
}

(goplay: http://play.golang.org/p/fApWQgtr4D)

答案2

得分: 4

你似乎期望接收goroutine在第二个fmt.Println执行之前完成运行。但是这并不能保证。如果程序终止,goroutine并不能保证会执行完其函数的末尾。

当你看到输出中没有显示"GOT IT"消息时,说明通道已经传递了消息,但是main函数在goroutine之前已经执行完毕。程序终止,goroutine就没有机会调用fmt.Printf

在你提到的示例中,main函数以如下方式结束:

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

由于main函数会阻塞直到_接收_到一条消息,所以在这个示例中,goroutine总是会完整地运行。而在你的代码中,goroutine在从通道接收后还执行了一步操作,无法确定是goroutine还是main函数会执行接收后的下一行代码。

英文:

You seem to be expecting the receiving goroutine to run to completion before the second fmt.Println executes. This is not guaranteed to be the case. If the program terminates, goroutines are not guaranteed to reach the end of their functions.

When you see the output that doesn't display the "GOT IT" message, the channel delivered its message, but the main function completed before the goroutine did. The program terminated, and the goroutine never gets the chance to call fmt.Printf

In the example you cited, the main function ends with this:

go func() { messages &lt;- &quot;ping&quot; }()
msg := &lt;-messages
fmt.Println(msg)

Since the main function blocks until it receives a message, the goroutine always runs to completion in this example. In your code, your goroutine executes a step after it receives from the channel, and it's undefined whether the goroutine or the main function will execute the next line after the receive.

答案3

得分: 4

你有两个goroutine,一个在main()函数中(隐式地成为一个goroutine),另一个是匿名的。

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

此时,在main() goroutine中剩下的代码如下:

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

而在匿名goroutine中剩下的代码如下:

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

现在你在竞争:这些Println的输出可能以任何顺序出现,甚至交错出现。当main中的Println()完成后,该goroutine上接下来要发生的事情是程序将停止。这可能会阻止匿名goroutine中的一些或全部Println的出现。

英文:

You have two goroutines, one in main() (which is implicitly a goroutine), and the anonymous one.

They communicate over a synchronous channel, so after the channel communication, it's guaranteed that they're synchronised.

At this point, the code left in the main() goroutine looks like this:

fmt.Println(&quot;[-] putting num on a channel&quot;)

and the code left in the anonymous goroutine looks like this:

fmt.Println(&quot;GOT IT: %d\n&quot;, number)

Now you're racing: the output from these Printlns may appear in either order, or even intermingled. When the Println() from main finishes, the next thing that will happen on that goroutine is that your program will be stopped. This may prevent some or all of the Println from the anonymous goroutine from appearing.

huangapple
  • 本文由 发表于 2013年12月8日 17:01:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/20451595.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定