Go通道:为什么有两个不同的输出?

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

Go channels: why two different outputs?

问题

我正在尝试理解Go语言中的通道。以下是一个代码示例:

package main

import "fmt"

func main() {
    m := make(map[int]string)
    m[2] = "First Value"
    c := make(chan bool)
    go func() {
        m[2] = "Second Value"
        c <- true
    }()
    fmt.Printf("1-%s\n", m[2])
    fmt.Printf("2-%s\n", m[2])
    _ = <-c
    fmt.Printf("3-%s\n", m[2])
    fmt.Printf("4-%s\n", m[2])
}

有时上述代码的输出结果是(结果1):

1-First Value
2-First Value
3-Second Value
4-Second Value

但有时我得到的是(结果2):

1-First Value
2-Second Value
3-Second Value
4-Second Value

c := make(chan bool)更改为c := make(chan bool, 1)后,情况仍然相同:有时是结果1,有时是结果2。

为什么会这样呢?

英文:

I'm trying to understand channels in Go. Here is a code example:

package main

import &quot;fmt&quot;

func main() {
	m := make(map[int]string)
	m[2] = &quot;First Value&quot;
	c := make(chan bool)
	go func() {
		m[2] = &quot;Second Value&quot;
		c &lt;- true
	}()
	fmt.Printf(&quot;1-%s\n&quot;, m[2])
	fmt.Printf(&quot;2-%s\n&quot;, m[2])
	_ = &lt;-c
	fmt.Printf(&quot;3-%s\n&quot;, m[2])
	fmt.Printf(&quot;4-%s\n&quot;, m[2])
}

Sometimes the output of the above code was (result 1):

1-First Value
2-First Value
3-Second Value
4-Second Value

but sometimes I got (result 2):

1-First Value
2-Second Value
3-Second Value
4-Second Value

After changing c := make(chan bool) to c := make(chan bool, 1), the same occurred: sometimes result 1, sometimes result 2.

Why?

答案1

得分: 4

你的结果完全合理。由于Go协程是相互独立运行的,你无法知道何时会开始执行Go协程。一旦执行了以下代码:

m[2] = "Second Value"

它将在你的主Go协程中反映出来。

因此,在你的程序的第一次和第二次打印之间执行上述代码时,你会看到以下结果:

1-First Value
2-Second Value
3-Second Value
4-Second Value

当不执行该代码时,你会看到另一种结果。在第三次打印之前,你确保其他Go协程已经完成。

为了更清楚地说明,如果你稍微修改一下你的程序,如下所示:

package main

import "fmt"
import "time"

func main() {
    m := make(map[int]string)
    m[2] = "First Value"
    c := make(chan bool)
    go func() {
        m[2] = "Second Value"
        c <- true
    }()
    time.Sleep(time.Second)
    fmt.Printf("1-%s\n", m[2])
    fmt.Printf("2-%s\n", m[2])
    _ = <-c
    fmt.Printf("3-%s\n", m[2])
    fmt.Printf("4-%s\n", m[2])
}

你很可能会得到以下输出:

1-Second Value
2-Second Value
3-Second Value
4-Second Value

希望对你有所帮助。

英文:

Your results makes perfect sense. As go routine run independent of each other, you would never know when go routine will start executing. As soon as line

m[2] = &quot;Second Value&quot;

is executed it will be reflected on your main go routine.

Hence when above line is executed between first and second print of your program you see result as

1-First Value
2-Second Value
3-Second Value
4-Second Value

When it is not you see other one. Before third print you ensure that other go routine is finished.

Just to clear it even more if you modify your program a little bit like

package main

import &quot;fmt&quot;
import &quot;time&quot;

func main() {
    m := make(map[int]string)
    m[2] = &quot;First Value&quot;
    c := make(chan bool)
    go func() {
        m[2] = &quot;Second Value&quot;
        c &lt;- true
    }()
    time.Sleep(time.Second)
    fmt.Printf(&quot;1-%s\n&quot;, m[2])
    fmt.Printf(&quot;2-%s\n&quot;, m[2])
    _ = &lt;-c
    fmt.Printf(&quot;3-%s\n&quot;, m[2])
    fmt.Printf(&quot;4-%s\n&quot;, m[2])
}

<kbd>Playground</kbd>

You will most probably get the following output

1-Second Value
2-Second Value
3-Second Value
4-Second Value

Hope it helps.

答案2

得分: 2

你的代码实际上只确保第三个和第四个打印显示第二个值。当你将通道的缓冲区更改为1时,实际上并没有改变任何内容。

让我们看看第一种情况。你有一个无缓冲通道。
你启动的goroutine改变了映射的内容。但是你不知道调度器何时运行它。这就是为什么有时你会看到一个结果,而其他时候会看到另一个结果。
调用:_ = <-c将确保goroutine已经运行。因为这个调用将阻塞,直到goroutine实际上在通道上写入了一些内容。这就是为什么你在最后两个打印中永远不会看到'First value'的原因。

当你使用带缓冲的通道时,唯一改变的是在写入通道后,goroutine将立即退出。其他什么都不会改变(在Effective Go中阅读)。

英文:

You code actually only makes sure that the third and the forth prints display the second value. When you change the channel to have a buffer of 1 it doesn't really change anything.

So let's look at the first case. You have an unbuffered channel.
The go routine you fire off changes the content of the map. But you have no idea when the scheduler will run it. That is why sometimes you see one result and an other time the other result.
The call: _ = &lt;-c will ensure that the go routine has ran. Because this call will block until the go routine actually wrote something on the channel. That's why you will never see 'First value' in the last 2 prints.

When you use a buffered channel the only thing that will change is that the go routine will immediately exit after writing to the channel. Nothing else (read it on Effective Go).

huangapple
  • 本文由 发表于 2015年8月19日 23:06:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/32099288.html
匿名

发表评论

匿名网友

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

确定