Concurrency and mutex locks in Go

Go is not a procedural language. If you write a piece of code before the other one, the first piece of code will be executed first. But, go makes it really easy to do things in parallel. I was working on an image library that was doing simple image operations like changing images to grayscale or filtering them by color channels and I wanted to try some of the concurrent programming techniques in go. Turns out, they are very easy to use. By the way check out my image lib, I think it's cool.github.com/KorayGocmen/image
Let me give you an example, here is what I started with. (Not going to get into image processing, that is a whole different area)
Non parallel
package main

import (
  "fmt"
)

func main() {
  for workerID := 0; workerID < 3; workerID++ {
    worker(workerID)
  }
}

func worker(workerID int) {
  for j := 0; j < 5; j++ {
    fmt.Println(workerID, "says", j)
  }
}
This code will print out the following
output
0 says 0
0 says 1
0 says 2
0 says 3
0 says 4
1 says 0
1 says 1
1 says 2
1 says 3
1 says 4
2 says 0
2 says 1
2 says 2
2 says 3
2 says 4
By adding one keyword "go", we can run the workers at the same time.
Non useful parallelism
package main

import (
  "fmt"
)

func main() {
  for workerID := 0; workerID < 3; workerID++ {
    go worker(workerID)
  }
}

func worker(workerID int) {
  for j := 0; j < 5; j++ {
    fmt.Println(workerID, "says", j)
  }
}
What will this print? Absolutely nothing. Because after firing up 3 workers back to back, main program exits. We need to "wait" until all workers finish. This where the sync package and the waitgroup comes into play.
package main

import (
  "fmt"
  "sync"
)

func main() {
  var wg sync.WaitGroup

  for workerID := 0; workerID < 3; workerID++ {
    wg.Add(1)
    go worker(workerID, &wg)
  }

  wg.Wait()
}

func worker(workerID int, wg *sync.WaitGroup) {
  for j := 0; j < 5; j++ {
    fmt.Println(workerID, "says", j)
  }

  wg.Done()
}
output
2 says 0
2 says 1
2 says 2
0 says 0
0 says 1
0 says 2
0 says 3
0 says 4
2 says 3
2 says 4
1 says 0
1 says 1
1 says 2
1 says 3
1 says 4
We create a sync.WaitGroup struct and add how many "Done" signals it needs to wait. I passed one signal for each worker I created in each for loop run. You can pass how many you want sync to wait before hand as well. When each worker finishes it's "job", it will call "Done" which will signal 1/3 done and then 2/3 done and finally 3/3 done. At that point the wg.Wait() will stop blocking and the program will exit.
If your workers are accessing shared data, you would need to use mutex locks to synchronize data mutations.
Without using mutex locks
package main

import (
  "fmt"
  "sync"
)

var (
  count = 0
)

func main() {
  var wg sync.WaitGroup

  for workerID := 0; workerID < 100; workerID++ {
    wg.Add(1)
    go worker(workerID, &wg)
  }

  wg.Wait()

  fmt.Println("Total count:", count)
}

func worker(workerID int, wg *sync.WaitGroup) {
  for j := 0; j < 5; j++ {
    incrCount()
  }

  wg.Done()
}

func incrCount() {
  count++
}
output
Total count: 495
Hmm, we were expecting 500. When we don't use a mutex lock, we create a race condition where while worker 1 is incrementing the count from 250 to 251, worker 2 happens to be doing the exact same thing, which basically results in missing an increment. Here is how to fix that.
Fixed by using mutex locks
package main

import (
  "fmt"
  "sync"
)

var (
  count      = 0
  countMutex = &sync.Mutex{}
)

func main() {
  var wg sync.WaitGroup

  for workerID := 0; workerID < 100; workerID++ {
    wg.Add(1)
    go worker(workerID, &wg)
  }

  wg.Wait()

  fmt.Println("Total count:", count)
}

func worker(workerID int, wg *sync.WaitGroup) {
  for j := 0; j < 5; j++ {
    incrCount()
  }

  wg.Done()
}

func incrCount() {
  countMutex.Lock()
  defer countMutex.Unlock()
  count++
}
output
Total count: 500
That seems right. Anyways, that is a basic example of how to use sync.Waitgroup and mutex locks to do concurrent stuff.
Koray Gocmen
Koray Gocmen

University of Toronto, Computer Engineering.

Architected and implemented reliable infrastructures and worked as the lead developer for multiple startups.