15

Have loook at this contrived example:

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++
    }
}

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?

icza
  • 389,944
  • 63
  • 907
  • 827
luqo33
  • 8,001
  • 16
  • 54
  • 107
  • 1
    [this blogpost](https://nathanleclaire.com/blog/2014/02/15/how-to-wait-for-all-goroutines-to-finish-executing-before-continuing/) captures different approaches. 1. using a channel that goroutines write to and main reads from, 2. using [sync.WaitGroup](https://golang.org/pkg/sync/#WaitGroup). There's also [this s/o answer](http://stackoverflow.com/a/18601301/1520594) for reference. – algrebe Mar 12 '17 at 20:33
  • @algrebe As noted by the article's reviewer and seconded by the author himself - both approaches described in the article originally are "dangerously inaccurate" so they cannot really constitute an answer to this question. – luqo33 Mar 12 '17 at 20:45

4 Answers4

26

Simplest, cleanest and "scalable" way to do it is to use a 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()
}

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
icza
  • 389,944
  • 63
  • 907
  • 827
  • Do you think it's a better approach using `var wg = &sync.WaitGroup{}` like you did or passing `wg` as a reference as I made in my snippet? If the first one, can you explain? Pretty curious about that. – AndreaM16 Mar 12 '17 at 20:57
  • 2
    @AndreaM16 I used a global var here for simplicity. If complexity of your code / package increases, or you use multiple waitgroups concurrently, then the global variable might not be viable or might make things more complicated than it should be. See edited answer. – icza Mar 12 '17 at 20:58
  • Thanks @icza also for sc2gears, used it a lot back in the days. Sorry if off topic. – AndreaM16 Mar 12 '17 at 21:05
3

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.

Uvelichitel
  • 8,220
  • 1
  • 19
  • 36
  • 6
    For the love of god, never EVER do this. Just spent the last 4 days trying to debug a program that was using over 200% CPU. This was it. – Dowland Aiello Jan 08 '19 at 02:01
  • @DowlandAiello Why? – nicky_zs Dec 31 '19 at 02:51
  • What system are you using? https://stackoverflow.com/a/18661952/14020202 says that it would just block and shouldn't consume any CPU. – janreggie Oct 03 '21 at 08:41
  • Local testing (nothing in prod) indicates that it doesn't use any CPU. The Go compiler source also indicates that the compiler just optimizes it into a blocking case: https://github.com/golang/go/blob/d9fd9201ad214e8da769a9338b9d3a5f3e1bc980/src/cmd/compile/internal/walk/select.go#L38 – Jon Bright Jun 13 '22 at 18:29
2

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 (
    "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()
}
AndreaM16
  • 3,917
  • 4
  • 35
  • 74
1

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

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')
}

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

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()
}
Alexander R.
  • 1,756
  • 12
  • 19