从goroutine中没有得到预期的输出。

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

Not getting expected output from goroutine

问题

我在(https://www.geeksforgeeks.org/channel-in-golang/)上读到了以下内容:

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

为了测试上述说法,我编写了下面提到的示例程序:

程序:

package main
import (
    "fmt"
    "sync"
    "time"
)

func myFunc(ch chan int) {
    fmt.Println("在goroutine中:: myFunc()")
    fmt.Println(10 + <-ch) // 根据规则,控制将在此处阻塞,直到'ch'发送一些数据,以便在我们的myFunc() goroutine中接收到它。
}
func main() {

    fmt.Println("开始Main方法")
    // 创建一个通道
    ch := make(chan int)

    go myFunc(ch) // 这个goroutine在一个新线程中启动

    time.Sleep(2 * time.Second) // 引入2秒的休眠,以确保myFunc() goroutine在主线程之前执行
    ch <- 10
    fmt.Println("结束Main方法")
}

我期望的输出是:

开始Main方法

在goroutine中:: myFunc()

20

结束Main方法

但是,实际输出是:

开始Main方法

在goroutine中:: myFunc()

结束Main方法

为什么通过通道发送的值没有打印出来?
我认为这是因为主线程先完成了执行,因此所有其他的goroutine也终止了。

如果是这样的话,那么为什么规则说 - 它允许goroutine在没有显式锁或条件变量的情况下进行同步

因为为了得到预期的输出,我必须使用sync.WaitGroup来告诉主线程等待其他goroutine完成。这是否违反了上述规则,因为我在使用锁的形式waitgroup?

PS:我正在学习golang。所以如果我完全理解错误,请原谅。

英文:

I read it on (https://www.geeksforgeeks.org/channel-in-golang/) that:

"In the channel, the send and receive operation block until another side is not ready by default.
It allows goroutine to synchronize with each other without explicit locks or condition variables."

To test above statement, I have written a sample program mentioned below:

Program:

package main
import (
    &quot;fmt&quot;
    &quot;sync&quot;
    &quot;time&quot;
)

func myFunc(ch chan int) {
    fmt.Println(&quot;Inside goroutine:: myFunc()&quot;)
    fmt.Println(10 + &lt;-ch) //&lt;-- According to rule, control will be blocked here until &#39;ch&#39; sends some data so that it will be received in our myFunc() go routine.
}
func main() {

    fmt.Println(&quot;Start Main method&quot;)
    // Creating a channel
    ch := make(chan int)

    go myFunc(ch) //&lt;-- This go routine started in a new thread

    time.Sleep(2 * time.Second) //&lt;--- introduced a Sleep of 2 seconds to ensure that myFunc() go routine executes before main thread
    ch &lt;- 10
    fmt.Println(&quot;End Main method&quot;)
}

I was expecting below output:

Start Main method

Inside goroutine:: myFunc()

20

End Main method

But, Actual output received is:

Start Main method

Inside goroutine:: myFunc()

End Main method

Why the value sent through channel is not printed?
I think, it is because main thread finished its execution first and hence, all other goroutine also terminated.

If that is the case, then, why does the rule said - It allows goroutine to synchronize with each other without explicit locks or condition variables.

Because, to get the expected output, I have to use sync.WaitGroup to tell the main thread to wait for the other goroutine to finish. Isn't it violating the above rule as I am using locks in form of waitgroup?

PS: I am learning golang. So please forgive if I get the concept totally wrong.

答案1

得分: 1

主goroutine在myFunc goroutine能够打印输出之前就已经退出了。下面是一个确保myFunc goroutine在主goroutine退出之前完成的实现。

package main

import (
	"fmt"
	"sync"
	"time"
)

func myFunc(ch chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println("在goroutine中:: myFunc()")
	fmt.Println(10 + <-ch) // 根据规则,控制将在此处阻塞,直到'ch'发送一些数据,以便在我们的myFunc() goroutine中接收到它。
}

func main() {
	fmt.Println("开始Main方法")
	// 创建一个通道
	ch := make(chan int)
	wg := sync.WaitGroup{}
	wg.Add(1)
	go myFunc(ch, &wg) // 这个goroutine在一个新线程中启动

	time.Sleep(2 * time.Second) // 引入2秒的延迟,以确保myFunc() goroutine在主线程之前执行
	ch <- 10
	wg.Wait()
	fmt.Println("结束Main方法")
}

这里使用通道进行同步,它按照文档中描述的方式工作。这并不意味着从代码的这一点开始的代码将以相同的速度执行。它只意味着如果myFunc goroutine没有从通道中读取数据,主goroutine将不会继续执行。而myFunc将等待主goroutine将数据推送到通道中。在这种情况下,两个goroutine将独立地继续执行。

英文:

The main goroutine exists before the myFunc goroutine is able to print the output. Here is an implementation which ensures that myFunc goroutine finishes before the main goroutine exits.

package main

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

func myFunc(ch chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println(&quot;Inside goroutine:: myFunc()&quot;)
	fmt.Println(10 + &lt;-ch) //&lt;-- According to rule, control will be blocked here until &#39;ch&#39; sends some data so that it will be received in our myFunc() go routine.
}
func main() {

	fmt.Println(&quot;Start Main method&quot;)
	// Creating a channel
	ch := make(chan int)
	wg := sync.WaitGroup{}
	wg.Add(1)
	go myFunc(ch, &amp;wg) //&lt;-- This go routine started in a new thread

	time.Sleep(2 * time.Second) //&lt;--- introduced a Sleep of 2 seconds to ensure that myFunc() go routine executes before main thread
	ch &lt;- 10
	wg.Wait()
	fmt.Println(&quot;End Main method&quot;)
}

The channels are used here for synchronization and it works as described in documentation. It does not mean that the code starting from this point in the code will be executed at the same speed. It only means that main goroutine will not continue if myFunc goroutine is not reading from channel. And myFunc will wait for main goroutine to push data to channel. After this happen both goroutines will continue it execution independently.

答案2

得分: 1

请尝试这样做,将你的代码作为基础:

package main

import (
	"fmt"
	"time"
)

func myFunc(ch chan int, done chan struct{}) {
	defer close(done) // 在函数退出阶段关闭通道
	fmt.Println("在 goroutine 中:: myFunc()")
	fmt.Println(10 + <-ch) // 根据规则,控制将在此处阻塞,直到 'ch' 发送一些数据,以便在我们的 myFunc() 协程中接收到它。
}

func main() {

	fmt.Println("开始 Main 方法")
	// 创建一个通道
	ch := make(chan int)
	done := make(chan struct{}) // 信号通道

	go myFunc(ch, done) // 这个协程在一个新线程中启动

	time.Sleep(2 * time.Second) // 引入 2 秒的休眠,以确保 myFunc() 协程在主线程之前执行
	ch <- 10
	<-done // 等待函数完成
	fmt.Println("结束 Main 方法")
}

或者使用 Jaroslaw 的建议。

英文:

Try this, used your code as basis

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func myFunc(ch chan int, done chan struct{}) {
	defer close(done) // channel will be closed in the function exit phase
	fmt.Println(&quot;Inside goroutine:: myFunc()&quot;)
	fmt.Println(10 + &lt;-ch) //&lt;-- According to rule, control will be blocked here until &#39;ch&#39; sends some data so that it will be received in our myFunc() go routine.
}
func main() {

	fmt.Println(&quot;Start Main method&quot;)
	// Creating a channel
	ch := make(chan int)
	done := make(chan struct{}) // signal channel

	go myFunc(ch, done) //&lt;-- This go routine started in a new thread

	time.Sleep(2 * time.Second) //&lt;--- introduced a Sleep of 2 seconds to ensure that myFunc() go routine executes before main thread
	ch &lt;- 10
	&lt;-done // waiting for function complete
	fmt.Println(&quot;End Main method&quot;)
}

Or use Jaroslaw's suggestion.

答案3

得分: 0

因为Go语言非常快速... https://play.golang.org/p/LNyDAA3mGYY

当你发送到通道的调度器不够快时... 程序会退出。我引入了一个额外的上下文切换器来展示调度器的效果。

英文:

Because go is so fast... https://play.golang.org/p/LNyDAA3mGYY

After you send to channel scheduler isn't fast enoght... and program exists. I have introduced an additional context switcher for scheduler to show effect.

答案4

得分: 0

是的,你是对的。

如果你检查上面的程序执行,你会发现在主线程写入通道之前有一个睡眠操作。现在,尽管哪个goroutine会获得CPU时间是完全随机的,但在上述情况下,如果mainexplicit sleep逻辑之前睡眠,myFunc将被阻塞,因为ch中没有数据。

在上面的代码中,我对代码进行了轻微的修改,使得main在向通道写入数据之后睡眠。这样就可以得到预期的输出,而不使用waitgroupquit channels

package main

import (
    "fmt"
    "time"
)

func myFunc(ch chan int) {
    fmt.Println("Inside goroutine:: myFunc()")
    fmt.Println(10 + <-ch) // 根据规则,控制将在此处阻塞,直到'ch'发送一些数据,以便在我们的myFunc() goroutine中接收到它。
}

func main() {
    fmt.Println("Start Main method")
    // 创建一个通道
    ch := make(chan int)

    go myFunc(ch) // 这个goroutine在一个新的线程中启动

    ch <- 10

    time.Sleep(2 * time.Second) // 引入2秒的睡眠,以确保myFunc() goroutine在主线程之前执行

    fmt.Println("End Main method")
}

希望对你有帮助!

英文:

Yes, you are right

>I think, it is because main thread finished its execution first and hence, all other goroutine also terminated.

If you check the above program execution. The sleep is before main thread writes to the channel. Now even though which goroutine() will have CPU time is completely arbitary, but in the above case if the main sleeps before the explicit sleep logic. myFunc will be blocked as there is no data in ch

Here I made a slight change to the above code to make main sleep after writing data into Channel. It gives the expected output, Without using waitgroup or quit channels.

package main

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

func myFunc(ch chan int) {
    fmt.Println(&quot;Inside goroutine:: myFunc()&quot;)
    fmt.Println(10 + &lt;-ch) //&lt;-- According to rule, control will be blocked here until &#39;ch&#39; sends some data so that it will be received in our myFunc() go routine.
}
func main() {

    fmt.Println(&quot;Start Main method&quot;)
    // Creating a channel
    ch := make(chan int)

    go myFunc(ch) //&lt;-- This go routine started in a new thread

   
    ch &lt;- 10
    
    time.Sleep(2 * time.Second) //&lt;--- introduced a Sleep of 2 seconds to ensure that myFunc() go routine executes before main thread


    fmt.Println(&quot;End Main method&quot;)
}

答案5

得分: -1

目前存在一个竞争条件,即myFunc能够打印输出和你的主函数退出之间的竞争。

如果我们查看程序执行的规范,可以在以下链接找到:
https://golang.org/ref/spec#Program_execution

程序执行从初始化主包开始,然后调用main函数。当该函数调用返回时,程序退出。它不会等待其他(非主)goroutine 完成。

你仍然需要确保在主goroutine退出之前,所有生成的goroutine都能够完成任务。

在你的情况下,你可以使用一个waitgroup,就像你提到的那样,或者你可以使用一个done通道。
https://play.golang.org/p/RVr0HXuUMgn

根据你的代码,你也可以关闭用于发送整数的通道,因为你将其作为双向参数传递给函数,但这不是严格符合惯例的做法。
https://play.golang.org/p/wGvexC5ZgIi

英文:

It is currently a race condition between the myFunc being able to print and your main function exiting.

If we look at the spec for program execution at
https://golang.org/ref/spec#Program_execution

> Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.

It is still your job to make sure that all spawned goroutines will complete before your main goroutine exists.

In your case, you could use a waitgroup as you mentioned or you could use a done channel.
https://play.golang.org/p/RVr0HXuUMgn

Given your code you could also close the channel you use to send the integer over since you are passing it to the function as bidirectional but it's not strictly idiomatic.
https://play.golang.org/p/wGvexC5ZgIi

huangapple
  • 本文由 发表于 2021年8月7日 17:28:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/68691084.html
匿名

发表评论

匿名网友

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

确定