Goroutine + channel执行顺序

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

Goroutine + channel execution order

问题

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

我是golang的新手,想深入了解channel的工作原理。
过去我用.net做过一个基本的多线程程序,所以我对goroutine有一些了解。

我认为goroutine是一种线程(实际上是一种绿色线程),它可以与其他goroutine并发运行,所以不能保证执行顺序。

但是我不明白的是当你结合goroutinechannel时会发生什么。

我知道你可以使用channel来保证goroutine的执行顺序,因为它可以阻塞goroutine

假设我有这样的代码:

func greet(c chan string) {
    fmt.Println("Before print 1st item")
    fmt.Println("Hello", <-c)
    fmt.Println("After print 1st item")

    fmt.Println("Before print 2nd item")
    fmt.Println("Hello", <-c)
    fmt.Println("After print 2nd item")
}

func main() {
    fmt.Println("Main start")
    c := make(chan string)

    go greet(c)

    fmt.Println("Before insert 1st item")
    c <- "Alpha" // 主goroutine在这里被阻塞,并切换到另一个goroutine。被阻塞是因为通道已满,或者我理解错了吗?
    fmt.Println("After insert 1st item")

    fmt.Println("Before insert 2nd item")
    c <- "Zebra" // 为什么这里没有被阻塞?
    fmt.Println("After insert 2nd item")

    time.Sleep(2 * time.Second)
    fmt.Println("Main end")
}

输出结果为:

Main start
Before insert 1st item
Before print 1st item
Hello Alpha
After print 1st item
Before print 2nd item
After insert 1st item
Before insert 2nd item
After insert 2nd item // 为什么会在这里打印?
Hello Zebra
After print 2nd item
Main end

为什么会这样打印?

我的预期是:

Main start
Before insert 1st item
Before print 1st item
Hello Alpha
After print 1st item
Before print 2nd item
After insert 1st item
Before insert 2nd item
Hello Zebra
After print 2nd item
After insert 2nd item
Main end

我理解的关键点是:

  1. main goroutine 在这一行 c <- "Alpha" 被阻塞,所以切换到了 greet goroutine。
  2. greet goroutine 将继续执行,直到在第二个 fmt.Println("Hello", <-c) 上被阻塞。
  3. 现在 greet goroutine 被阻塞,而 main goroutine 没有被阻塞,因为通道中的字符串 Alpha 已经被读取,所以它将继续执行。

我的问题是为什么 main goroutine 在这一行 c <- "Zebra" 上没有被阻塞?

我预期它会像步骤2那样再次被阻塞,并切换到 greet goroutine。

根据这个文档

默认情况下,发送和接收操作会阻塞,直到另一侧准备就绪。这允许goroutine在没有显式锁或条件变量的情况下进行同步。

英文:

I'm new to golang and want to understand deeply how channel works.
In the past I've made a basic multithreaded program with .net so I kinda now what goroutine is

I assume that goroutine is some kind of thread (actually a green thread) that will run concurrently with other goroutine so there's no guarantee for the execution order

but what I don't understand when you combine goroutine and channel

I know that you can use channel to guarantee the execution order of goroutine because it can block goroutine

Lets say I have a code like this

func greet(c chan string) {

    fmt.Println(&quot;Before print 1st item&quot;)
    fmt.Println(&quot;Hello&quot;, &lt;-c)
    fmt.Println(&quot;After print 1st item&quot;)

    fmt.Println(&quot;Before print 2nd item&quot;)
    fmt.Println(&quot;Hello&quot;, &lt;-c)
    fmt.Println(&quot;After print 2nd  item&quot;)

}

func main() {
    fmt.Println(&quot;Main start&quot;)
    c := make(chan string)

     go greet(c)

    fmt.Println(&quot;Before insert 1st item&quot;)
    c &lt;- &quot;Alpha&quot; // main goroutine blocked here and will be switched to another goroutine. Blocked because the channel is full or maybe I&#39;m wrong here?
    fmt.Println(&quot;After insert 1st item&quot;)

    fmt.Println(&quot;Before insert 2nd item&quot;)
    c &lt;- &quot;Zebra&quot; // why is it not blocked here ?
    fmt.Println(&quot;After insert 2nd item&quot;)

    time.Sleep(2 * time.Second)
    fmt.Println(&quot;Main end&quot;)
}

//Output
Main start
Before insert 1st item
Before print 1st item
Hello Alpha
After print 1st item
Before print 2nd item
After insert 1st item
Before insert 2nd item
After insert 2nd item // why is it printed here ???
Hello Zebra
After print 2nd  item
Main end

Why is it printed like that ???

// My expectation
Main start
Before insert 1st item
Before print 1st item
Hello Alpha
After print 1st item
Before print 2nd item
After insert 1st item
Before insert 2nd item
Hello Zebra
After print 2nd  item
After insert 2nd item 
Main end

KeyPoint from what I understand

  1. The main gouroutine blocked on this line c &lt;- &quot;Alpha&quot; so it switched to greet goroutine
  2. The greet goroutine will continue to execute until it blocked on the second fmt.Println(&quot;Hello&quot;, &lt;-c)
  3. Now greet goroutine is blocked and the main goroutine is not blocked because the string Alpha in channel already read and it will continue to execute

My question is why main goroutine is not blocked on this line c &lt;- &quot;Zebra&quot; ?

I expected it to be blocked again just like in step 2 and will switch to greet goroutine

Based on this doc
> By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.

答案1

得分: 1

假设通道就像一张桌子。当有人等待从通道接收时,就好像你想从桌子上拿走东西。如果有东西,你就拿走然后离开;如果没有东西,你必须等待别人把东西放在那里。当你想要发送到通道时,情况也是一样的。如果桌子上没有东西,你就把你手上的东西放上去然后离开;如果桌子上有东西,你必须等待别人把东西拿走以便放上你的东西。(记住,你可以有一个可以容纳多个东西的通道)。

一旦你被释放,你可以随心所欲地做任何事情,不需要等待或同步。所以你无法预先知道会发生什么,因为取决于你被分配的处理周期,你可能会在其他例程之前或之后到达。

如果你多次运行这个程序,你会发现不同的输出出现。

英文:

Let's say channels are like a table. When somebody waits to receive from it, it is like you want to take something from the table. If there is anything, you take it and go, if there is nothing, you must wait for someone to put it over there. Same happens when you want to send at the channel. If there is nothing over the table, you just put the thing you have and leave, if there is something on it, you must wait for someone to take it to put your thing. (remember you can have a channel with space for more that one thing).

At the moment you get liberated, you keep doing whatever you want, no need to wait or syncronize. So you can't know what will happen before, because depending on the processing cycles you have been assigned you will get there before or after the other routines.

If you launch that a few times, you will see that different output appears.

huangapple
  • 本文由 发表于 2022年2月22日 17:28:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/71218782.html
匿名

发表评论

匿名网友

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

确定