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
  • 342,548
  • 56
  • 784
  • 738
luqo33
  • 7,196
  • 13
  • 50
  • 101
  • 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

23

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
  • 342,548
  • 56
  • 784
  • 738
  • 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
  • 7,778
  • 1
  • 16
  • 34
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,719
  • 4
  • 34
  • 71
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,668
  • 12
  • 18