【问题标题】:No output from goroutinegoroutine 没有输出
【发布时间】:2015-05-11 13:33:07
【问题描述】:

SayHello() 按预期执行时,goroutine 什么也不打印。

package main

import "fmt"

func SayHello() {
    for i := 0; i < 10 ; i++ {
        fmt.Print(i, " ")
    }
}

func main() {
    SayHello()
    go SayHello()
}

【问题讨论】:

标签: go concurrency goroutine


【解决方案1】:

当您的main() 函数结束时,您的程序也会结束。它不会等待其他 goroutine 完成。

引用Go Language Specification: Program Execution:

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

更多详情请见this answer

您必须告诉您的 main() 函数等待作为 goroutine 启动的 SayHello() 函数完成。您可以将它们与频道同步,例如:

func SayHello(done chan int) {
    for i := 0; i < 10; i++ {
        fmt.Print(i, " ")
    }
    if done != nil {
        done <- 0 // Signal that we're done
    }
}

func main() {
    SayHello(nil) // Passing nil: we don't want notification here
    done := make(chan int)
    go SayHello(done)
    <-done // Wait until done signal arrives
}

另一种选择是通过关闭通道来表示完成:

func SayHello(done chan struct{}) {
    for i := 0; i < 10; i++ {
        fmt.Print(i, " ")
    }
    if done != nil {
        close(done) // Signal that we're done
    }
}

func main() {
    SayHello(nil) // Passing nil: we don't want notification here
    done := make(chan struct{})
    go SayHello(done)
    <-done // A receive from a closed channel returns the zero value immediately
}

注意事项:

根据您的编辑/cmets:如果您希望 2 个正在运行的 SayHello() 函数随机打印“混合”数字:您无法保证观察到这种行为。同样,请参阅aforementioned answer 了解更多详细信息。 Go Memory Model 只保证某些事件在其他事件之前发生,你无法保证 2 个并发 goroutines 是如何执行的。

您可能会尝试使用它,但要知道结果不会是确定性的。首先,您必须启用多个活动的 goroutine 来执行:

runtime.GOMAXPROCS(2)

其次,您必须首先将 SayHello() 作为 goroutine 启动,因为您当前的代码首先在主 goroutine 中执行 SayHello(),并且只有在它完成后才会启动另一个:

runtime.GOMAXPROCS(2)
done := make(chan struct{})
go SayHello(done) // FIRST START goroutine
SayHello(nil) // And then call SayHello() in the main goroutine
<-done // Wait for completion

【讨论】:

  • 对,我在阅读dave.cheney.net/2014/03/19/channel-axioms 后才理解您的编辑:向nil 频道发送永远阻塞,从nil 频道接收永远阻塞。
  • @DineshPanchananam 你所说的“无序”时尚是什么意思?您希望从 2 个正在运行的 SayHello() 函数中随机看到混合数字吗?
  • @DineshPanchananam 我编辑了我的答案来解决这个问题。但简而言之,您无法保证观察到此类行为(即使您这样做,也不是确定性的)。
【解决方案2】:

或者(对 icza 的回答)您可以使用 sync 包中的 WaitGroup 和匿名函数来避免更改原始 SayHello

package main

import (
    "fmt"
    "sync"
)

func SayHello() {
    for i := 0; i < 10; i++ {
        fmt.Print(i, " ")
    }
}

func main() {
    SayHello()

    var wg sync.WaitGroup
    wg.Add(1)

    go func() {
        defer wg.Done()
        SayHello()
    }()

    wg.Wait()
}

为了同时打印数字,在单独的例程中运行每个打印语句,如下所示

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(fnScopeI int) {
            defer wg.Done()

            // next two strings are here just to show routines work simultaneously
            amt := time.Duration(rand.Intn(250))
            time.Sleep(time.Millisecond * amt)

            fmt.Print(fnScopeI, " ")
        }(i)
    }

    wg.Wait()
}

【讨论】:

  • @DineshPanchananam 你想在单独的例程中打印每个数字吗?
  • 没有。我期待一些“平行”的东西。但我认为我错了。
  • @DineshPanchananam 检查答案的第二部分
  • 另外,当只有一个函数调用(第一个代码示例)时,请不要使用延迟 - 只需将 wg.Done() 移动到函数调用之后。在这种情况下不需要使用延迟。
【解决方案3】:

main 函数返回时,Go 程序退出。

一种选择是使用类似sync.WaitGroup 的东西来等待main 产生的其他goroutine,然后再从main 返回。

另一种选择是在main 中调用runtime.Goexit()。来自godoc

Goexit 终止调用它的 goroutine。没有其他 goroutine 受到影响。 Goexit 在终止 goroutine 之前运行所有延迟调用。因为 Goexit 不是恐慌,所以这些延迟函数中的任何恢复调用都将返回 nil。

从主 goroutine 调用 Goexit 会终止该 goroutine 而不会返回 func main。由于 func main 没有返回,程序继续执行其他 goroutine。如果所有其他 goroutine 退出,程序就会崩溃。

这允许主 goroutine 停止执行,而后台例程继续执行。例如:

package main

import (
    "fmt"
    "runtime"
    "time"
)

func f() {
    for i := 0; ; i++ {
        fmt.Println(i)
        time.Sleep(10 * time.Millisecond)
    }
}

func main() {
    go f()
    runtime.Goexit()
}

这比在主函数中永远阻塞更干净,尤其是对于无限的程序。一个缺点是,如果一个进程的所有 goroutine 都返回或退出(包括主 goroutine),Go 会将此检测为错误和恐慌:

fatal error: no goroutines (main called runtime.Goexit) - deadlock!

为避免这种情况,至少有一个 goroutine 必须在返回之前调用 os.Exit。调用os.Exit(0) 会立即终止程序并表明它这样做没有错误。例如:

package main

import (
    "fmt"
    "os"
    "runtime"
    "time"
)

func f() {
    for i := 0; i < 10; i++ {
        fmt.Println(i)
        time.Sleep(10 * time.Millisecond)
    }
    os.Exit(0)
}

func main() {
    go f()
    runtime.Goexit()
}

【讨论】:

    猜你喜欢
    • 2021-10-11
    • 1970-01-01
    • 2020-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多