【问题标题】:Not getting expected output from goroutine没有从 goroutine 获得预期的输出
【发布时间】:2021-10-11 21:48:56
【问题描述】:

我在 (https://www.geeksforgeeks.org/channel-in-golang/) 上读到:

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

为了测试上面的语句,我写了一个下面提到的示例程序:

程序:

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

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

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

    go myFunc(ch) //<-- This go routine started in a new thread

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

我期待以下输出:

Start Main method

Inside goroutine:: myFunc()

20

End Main method

但是,收到的实际输出是:

Start Main method

Inside goroutine:: myFunc()

End Main method

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

如果是这样,那么,为什么规则说 - 它允许 goroutine 彼此同步无需显式锁或条件变量

因为,为了得到预期的输出,我必须使用sync.WaitGroup 告诉主线程等待另一个 goroutine 完成。我使用的是waitgroup形式的锁,是不是违反了上面的规则?

PS:我正在学习 golang。因此,如果我完全错误地理解了这个概念,请原谅。

【问题讨论】:

  • 我认为当接收方接收到值时,它会立即解除对发送方和接收方的阻塞。当 goroutine 运行时,main 函数继续打印并退出。如果 goroutine 能够及时打印,您将看到它或 main func 退出。尝试在本地多次运行您的代码,您将在两者之间看到20
  • In the channel, the send and receive operation block until another side is not ready by default 中的措辞不是很好。他们一直阻塞,直到对方准备好读取/推送。

标签: go goroutine


【解决方案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("Inside goroutine:: myFunc()")
    fmt.Println(10 + <-ch) //<-- According to rule, control will be blocked here until 'ch' sends some data so that it will be received in our myFunc() go routine.
}
func main() {

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

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

通道在这里用于同步,它的工作原理如文档中所述。这并不意味着代码中从这一点开始的代码将以相同的速度执行。这只意味着如果myFunc goroutine 没有从通道读取,主 goroutine 将不会继续。 myFunc 将等待主 goroutine 将数据推送到通道。发生这种情况后,两个 goroutine 将继续独立执行。

【讨论】:

    【解决方案2】:

    试试这个,以你的代码为基础

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func myFunc(ch chan int, done chan struct{}) {
        defer close(done) // channel will be closed in the function exit phase
        fmt.Println("Inside goroutine:: myFunc()")
        fmt.Println(10 + <-ch) //<-- According to rule, control will be blocked here until 'ch' sends some data so that it will be received in our myFunc() go routine.
    }
    func main() {
    
        fmt.Println("Start Main method")
        // Creating a channel
        ch := make(chan int)
        done := make(chan struct{}) // signal channel
    
        go myFunc(ch, done) //<-- This go routine started in a new thread
    
        time.Sleep(2 * time.Second) //<--- introduced a Sleep of 2 seconds to ensure that myFunc() go routine executes before main thread
        ch <- 10
        <-done // waiting for function complete
        fmt.Println("End Main method")
    }
    

    或者使用 Jaroslaw 的建议。

    【讨论】:

      【解决方案3】:

      因为 go 太快了...https://play.golang.org/p/LNyDAA3mGYY

      发送到频道后,调度程序不够快......并且程序存在。我已经为调度器引入了一个额外的上下文切换器来显示效果。

      【讨论】:

        【解决方案4】:

        是的,你是对的

        我想,这是因为主线程先完成了它的执行,因此所有其他 goroutine 也终止了。

        如果检查上述程序执行。睡眠是在主线程写入通道之前。现在即使哪个 goroutine() 将拥有 CPU 时间也是完全任意的,但在上述情况下,如果 mainexplicit sleep 逻辑之前休眠。 myFunc 将被阻止,因为ch 中没有数据

        这里我对上面的代码做了一点改动,让main在将数据写入Channel后休眠。它给出了预期的输出,而不使用waitgroupquit channels

        package main
        
        import (
            "fmt"
            "time"
        )
        
        func myFunc(ch chan int) {
            fmt.Println("Inside goroutine:: myFunc()")
            fmt.Println(10 + <-ch) //<-- According to rule, control will be blocked here until 'ch' sends some data so that it will be received in our myFunc() go routine.
        }
        func main() {
        
            fmt.Println("Start Main method")
            // Creating a channel
            ch := make(chan int)
        
            go myFunc(ch) //<-- This go routine started in a new thread
        
           
            ch <- 10
            
            time.Sleep(2 * time.Second) //<--- introduced a Sleep of 2 seconds to ensure that myFunc() go routine executes before main thread
        
        
            fmt.Println("End Main method")
        }
        

        【讨论】:

          【解决方案5】:

          当前是 myFunc 能够打印和您的主函数退出之间的竞争条件。

          如果我们查看程序执行的规范 https://golang.org/ref/spec#Program_execution

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

          确保所有派生的 goroutine 在主 goroutine 存在之前完成仍然是你的工作。

          在您的情况下,您可以使用您提到的等待组,也可以使用已完成的频道。 https://play.golang.org/p/RVr0HXuUMgn

          鉴于您的代码,您还可以关闭用于发送整数的通道,因为您将它作为双向传递给函数,但这并不是严格的惯用方式。 https://play.golang.org/p/wGvexC5ZgIi

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-05-11
            相关资源
            最近更新 更多