Goroutine is one of the most interesting features that Go has. Are a lightweight thread of an execution that allow us to program concurrent applications without having to deal with complex threads or parallelism since is everything handled by Go runtime.

Let’s play a bit with it so we can see different characteristics.

We’ll start with this simple hello world application:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, playground")
}

Output:

Hello, playground

How to spin up a goroutine

Is as easy as adding go before the function call:

func main() {
	go func() {
		fmt.Println("Hello, playground")
	}()
}

Output:

So, why is not returning anything now?

Since goroutine it’s like a new thread the script is not waiting for the goroutine to finish, we have to tell the main thread to wait for the goroutine.

Let’s add a sleep to be sure that is what we’re saying:

package main

import (
	"fmt"
	"time"
)

func main() {
	go func() {
		fmt.Println("Hello, playground")
	}()
	time.Sleep(1000)
}

Output:

Hello, playground

Now is working! But we can’t be adding sleeps since we can’t know how many time it would take any goroutine.

Waiting for the goroutine to finish

To do so we have several ways, let’s review all of them.

WaitGroups

WaitGroups allow us to wait until all the goroutines have finished. Are very easy to use.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	
	wg.Add(1)
	
	go func() {
		defer wg.Done()
		fmt.Println("Hello, playground")
	}()
	
	wg.Wait()
}

Output:

Hello, playground

fatal error: all goroutines are asleep - deadlock!

This is a very typical error we might get when working with WaitGroups. It happens when the program reach wg.Wait() but there are no pending goroutines to wait for.

Example of the error:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	
	wg.Add(1)
	
	go func() {
		fmt.Println("Hello, playground")
	}()
	
	wg.Wait()
}

Output:

Hello, playground
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000100018)
	/usr/local/go-faketime/src/runtime/sema.go:56 +0x45
sync.(*WaitGroup).Wait(0xc000100010)
	/usr/local/go-faketime/src/sync/waitgroup.go:130 +0x65
main.main()
	/tmp/sandbox871907795/prog.go:17 +0x73

Channels

Channels are a tool that allow us to communicate between goroutines. For example we can have one goroutine sending information to a channel and another one reading from it.

In this example we’ll use a channel to make the main thread wait for the goroutine since an unbuffered channel will always wait until data is received.

package main

import (
	"fmt"
)

func main() {
	done := make(chan bool)

	
	go func() {
		fmt.Println("Hello, playground")
		
		done <- true
	}()
	
	<- done
}

Output:

Hello, playground

Resources