Very basic concurrency for beginners in Go

  • The `go` keyword
  • A common gotcha when writing concurrent code
  • How to use the `sync.WaitGroup` to make sure our program doesn’t terminate prematurely
  • What kind of impact concurrent code can have on performance
  • How making code run concurrently introduces some unpredictability

The go keyword

func main(){
doSomething()
}
func main(){
go doSomething()
}

Simple example

package mainimport (
"fmt"
)
func main() {
fmt.Println("start")
doSomething()
fmt.Println("end")
}
func doSomething() {
fmt.Println("do something")
}
start
do something
end
func main() {
fmt.Println(“start”)
go doSomething()
fmt.Println(“end”)
}
start
end

In Go, when the main function exits, the program stops.

Wait groups

package mainimport (
“fmt”
“sync”
)
var wg sync.WaitGroupfunc main() {
fmt.Println(“start”)
wg.Add(1) // indicate we are going to wait for one thing
go doSomething()
fmt.Println(“end”)
wg.Wait() // wait for all things to be done
// end of program
}
func doSomething() {
fmt.Println(“do something”)
wg.Done() // this is done
}
  • var wg sync.WaitGroup — defines a WaitGroup that is ready to use
  • wg.Add(1) — indicates that there is 1 thing to wait for (our doSomething function)
  • wg.Wait() — indicates that code should block until the WaitGroup counter reaches zero
  • wg.Done() — indicates that 1 thing has finished
start
end
do something

How quickly can we do our multiplication tables?

package mainimport (
"fmt"
"time"
)
func main() {
timestable(2)
}
func timestable(x int) {
for i := 1; i <= 12; i++ {
fmt.Printf(“%d x %d = %d\n”, i, x, x*i)
time.Sleep(100 * time.Millisecond)
}
}
$ go run times.go
1 x 2 = 2
2 x 2 = 4
3 x 2 = 6
4 x 2 = 8
5 x 2 = 10
6 x 2 = 12
7 x 2 = 14
8 x 2 = 16
9 x 2 = 18
10 x 2 = 20
11 x 2 = 22
12 x 2 = 24
func main() {
for n := 2; n <= 12; n++ {
timestable(n)
}
}

How long does it take?

$ time go run times.go
real 0m13.762s
user 0m0.261s
sys 0m0.059s

Multiplying concurrency

package mainimport (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroupfunc main() {
for n := 2; n <= 12; n++ {
wg.Add(1)
go timestable(n)
}
wg.Wait()
}
func timestable(x int) {
for i := 1; i <= 12; i++ {
fmt.Printf("%d x %d = %d\n", i, x, x*i)
time.Sleep(100 * time.Millisecond)
}
wg.Done()
}
$ time go run times.go
real 0m1.431s
user 0m0.253s
sys 0m0.053s

The order is confusing

12 x 7 = 84
12 x 6 = 72
12 x 3 = 36
12 x 8 = 96
12 x 12 = 144
12 x 4 = 48
12 x 9 = 108
12 x 10 = 120
12 x 2 = 24
12 x 5 = 60
12 x 11 = 132

Conclusion

  • Next, you should learn about Channels.

--

--

--

Founder at MachineBox.io — Gopher, developer, speaker, author — BitBar app https://getbitbar.com — Author of Go Programming Blueprints

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mat Ryer

Mat Ryer

Founder at MachineBox.io — Gopher, developer, speaker, author — BitBar app https://getbitbar.com — Author of Go Programming Blueprints

More from Medium

Golang default settings

How to exit a child goroutine?

[Crazy Go Day] Simple Golang Unit Test Implementation