3 minutes
Go: Goroutines, Channels and WaitGroups
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