防止 main() 函数在 Golang 中的 goroutines 完成之前终止

Prevent the main() function from terminating before goroutines finish in Golang

看看这个人为的例子:

package main

import "fmt"

func printElo() {
    fmt.Printf("Elo\n")
}

func printHello() {
    fmt.Printf("Hello\n")
}

func main() {
    fmt.Printf("This will print.")
    i := 0
    for i < 10 {
        go printElo()
        go printHello()
        i++
    }
}

这个程序的输出只是 "This will print"。 goroutines printElo()printHello 的输出将不会发出,因为我猜 main() 函数线程将在 goroutines 有机会开始执行之前完成。

使类似代码在 Golang 中工作并且不会过早终止的惯用方法是什么?

最简单、最干净和“可扩展”的方法是使用 sync.WaitGroup:

var wg = &sync.WaitGroup{}

func printElo() {
    defer wg.Done()
    fmt.Printf("Elo\n")
}

func printHello() {
    defer wg.Done()
    fmt.Printf("Hello\n")
}

func main() {
    fmt.Printf("This will print.")
    i := 0
    for i < 10 {
        wg.Add(1)
        go printElo()
        wg.Add(1)
        go printHello()
        i++
    }
    wg.Wait()
}

输出(在 Go Playground 上尝试):

This will print.Hello
Elo
Hello
Elo
Hello
Elo
Hello
Elo
Hello
Elo
Hello
Elo
Hello
Elo
Hello
Elo
Hello
Elo
Hello
Elo

使用 sync.WaitGroup 时要遵循的简单“规则”:

  • 调用WaitGroup.Add() in the "original" goroutine (that starts a new) before the go语句
  • 建议延迟调用 WaitGroup.Done(),这样即使 goroutine 出现 panic 也会被调用
  • 如果你想将 WaitGroup 传递给其他函数(而不是使用包级变量),你必须传递一个指向它的指针,否则 WaitGroup(它是一个结构)会被复制,在副本上调用的 Done() 方法不会在原始
  • 上观察到

如果您只想玩玩结果,您可以使用 "hack" 等待输入:

package main

import (
    "fmt"
    "bufio"
    "os"
)

func printElo() {
    fmt.Printf("Elo\n")
}

func printHello() {
    fmt.Printf("Hello\n")
}

func main() {
    fmt.Printf("This will print.")
    i := 0
    for i < 10 {
        go printElo()
        go printHello()
        i++
    }

    reader := bufio.NewReader(os.Stdin)
    reader.ReadString('\n')
}

如果想了解如何进行同步,请阅读同步包:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func printElo() {
    fmt.Printf("Elo\n")
    wg.Done()
}

func printHello() {
    fmt.Printf("Hello\n")
    wg.Done()
}

func main() {

    fmt.Printf("This will print.")
    i := 0
    for i < 10 {
        wg.Add(2)
        go printElo()
        go printHello()
        i++
    }

    wg.Wait()
}

您可以使用sync package and take a look at waitgroups. You can take a look at a working Goplayground I set up.

基本上

package main

import (
    "fmt"
    "sync"
    )

//Takes a reference to the wg and sleeps when work is done
func printElo(wg *sync.WaitGroup) {
    fmt.Printf("Elo\n")
    defer wg.Done()
}

//Takes a reference to the wg and sleeps when work is done
func printHello(wg *sync.WaitGroup) {
    fmt.Printf("Hello\n")
    defer wg.Done()
}

func main() {
    //Create a new WaitGroup
    var wg sync.WaitGroup
    fmt.Println("This will print.")

    for  i := 0; i < 10; i++ {
        //Add a new entry to the waitgroup
        wg.Add(1)
        //New Goroutine which takes a reference to the wg
        go printHello(&wg)
        //Add a new entry to the waitgroup
        wg.Add(1)
        //New Goroutine which takes a reference to the wg
        go printElo(&wg)
    }
    //Wait until everything is done
    wg.Wait()
}

如前所述,sync.WaitGroup 是生产代码中的正确方法。但是当开发用于测试和调试目的时,您可以在末尾添加 select{} 语句或 main().

func main(){
    go routine()
    ...
    select{}
}

main() 然后永远不会 returns 你会用例如 Ctrl-C 杀死它。它不是惯用的,从未在生产中使用过,但在开发时非常快速简单。