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

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

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"。printElo()printHello()的goroutine的输出不会被打印出来,因为我猜测main()函数的线程会在goroutine有机会开始执行之前就结束。

在Golang中,如何以惯用的方式使类似的代码正常工作而不会过早终止?

英文:

Have loook at this contrived example:

package main

import &quot;fmt&quot;

func printElo() {
	fmt.Printf(&quot;Elo\n&quot;)
}

func printHello() {
	fmt.Printf(&quot;Hello\n&quot;)
}

func main() {
	fmt.Printf(&quot;This will print.&quot;)
	i := 0
	for i &lt; 10 {
		go printElo()
		go printHello()
		i++
	}
}

The output of this program would be just "This will print". Output of goroutines printElo() and printHello will not be emitted because, I guess, the main() function thread will finish before the goroutines have a chance to even start executing.

What is the idiomatic way to make similar code work in Golang and not terminate prematurely?

答案1

得分: 26

最简单、最清晰和可扩展的方法是使用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

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

  • 在“原始”goroutine(启动新的goroutine之前)中调用WaitGroup.Add()
  • 建议使用延迟调用WaitGroup.Done(),这样即使goroutine发生恐慌,也会被调用
  • 如果要将WaitGroup传递给其他函数(而不是使用包级别的变量),必须传递一个指针,否则WaitGroup(一个结构体)将被复制,对副本调用Done()方法不会影响原始的WaitGroup
英文:

Simplest, cleanest and "scalable" way to do it is to use a sync.WaitGroup:

var wg = &amp;sync.WaitGroup{}

func printElo() {
	defer wg.Done()
	fmt.Printf(&quot;Elo\n&quot;)
}

func printHello() {
	defer wg.Done()
	fmt.Printf(&quot;Hello\n&quot;)
}

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

Output (try it on the 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

Simple "rules" to follow when doing it with sync.WaitGroup:

  • call WaitGroup.Add() in the "original" goroutine (that starts a new) before the go statement

  • recommended to call WaitGroup.Done() deferred, so it gets called even if the goroutine panics

  • if you want to pass WaitGroup to other functions (and not use a package level variable), you must pass a pointer to it, else the WaitGroup (which is a struct) would be copied, and the Done() method called on the copy wouldn't be observed on the original

答案2

得分: 3

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

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

然后,main() 永远不会返回,您可以使用例如 Ctrl-C 来终止它。这不是惯用的做法,不会在生产环境中使用,但在开发过程中是一种非常快速简便的方法。

英文:

As already mentioned sync.WaitGroup is a right way in production code. But when developing for test and debug purposes you can just add select{} statement at the end or the main().

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

main() then never returns and you would kill it with for example Ctrl-C. It isn't idiomatic, never used in production, but just very quick easy hack when developing.

答案3

得分: 2

你可以使用同步包并查看waitgroups。你可以查看我设置的一个工作中的Goplayground

基本上

package main

import (
    "fmt"
    "sync"
)

//当工作完成时,接收到wg的引用并休眠
func printElo(wg *sync.WaitGroup) {
    fmt.Printf("Elo\n")
    defer wg.Done()
}

//当工作完成时,接收到wg的引用并休眠
func printHello(wg *sync.WaitGroup) {
    fmt.Printf("Hello\n")
    defer wg.Done()
}

func main() {
    //创建一个新的WaitGroup
    var wg sync.WaitGroup
    fmt.Println("This will print.")

    for i := 0; i < 10; i++ {
        //向waitgroup添加一个新条目
        wg.Add(1)
        //新的Goroutine,接收到wg的引用
        go printHello(&wg)
        //向waitgroup添加一个新条目
        wg.Add(1)
        //新的Goroutine,接收到wg的引用
        go printElo(&wg)
    }
    //等待直到所有工作完成
    wg.Wait()
}
英文:

You can use sync package and take a look at waitgroups. You can take a look at a working Goplayground I set up.

Essentially

package main

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

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

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

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

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

答案4

得分: 1

如果你只是想玩一下结果,可以使用等待输入的“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')
}

如果想学习如何进行同步,请阅读有关 sync 包的内容:

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()
}
英文:

If you want just to play with results you can use "hack" with waiting for input:

package main

import (
	&quot;fmt&quot;
	&quot;bufio&quot;
	&quot;os&quot;
)

func printElo() {
	fmt.Printf(&quot;Elo\n&quot;)
}

func printHello() {
	fmt.Printf(&quot;Hello\n&quot;)
}

func main() {
	fmt.Printf(&quot;This will print.&quot;)
	i := 0
	for i &lt; 10 {
		go printElo()
		go printHello()
		i++
	}

	reader := bufio.NewReader(os.Stdin)
	reader.ReadString(&#39;\n&#39;)
}

If want to learn how to do synchronization read about sync package:

package main

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

var wg sync.WaitGroup

func printElo() {
	fmt.Printf(&quot;Elo\n&quot;)
	wg.Done()
}

func printHello() {
	fmt.Printf(&quot;Hello\n&quot;)
	wg.Done()
}

func main() {

	fmt.Printf(&quot;This will print.&quot;)
	i := 0
	for i &lt; 10 {
		wg.Add(2)
		go printElo()
		go printHello()
		i++
	}

	wg.Wait()
}

huangapple
  • 本文由 发表于 2017年3月13日 04:28:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/42752705.html
匿名

发表评论

匿名网友

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

确定