What is a Mutex in Golang?

What is a Mutex in Golang?

When you build Golang programs, you will almost always find a use for Goroutines.

Goroutines are powerful and generally easy to use, however, if you need to modify some piece of data that is shared between Goroutines - then you may run into some trouble with data integrity.

In this article, we will look at what is a "Mutex" and how to use it.

What is a Mutex?

In Golang; a Goroutine is essentially a function that is put in a background queue and executed concurrently whenever resources are available.

package main

import (
    "fmt"
    "sync"
)

var NUM_PROCESSED = 0

func countNumProcessed(wg *sync.WaitGroup) {
    defer wg.Done()
    NUM_PROCESSED++
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 500; i++ {
        wg.Add(1)
        go countNumProcessed(&wg)
    }

    wg.Wait()
    fmt.Println(NUM_PROCESSED)
}

If we remove the "go" keyword, each function call to "countNumProcessed" will block the loop and wait for this function to finish before continuing with the loop.

When you use the "go" keyword, these functions will be run concurrently. Which then makes it possible for 2 or more functions to modify the variable at the same time.

If you run this code a few times - you will notice that the total count fluctuates.

This is because each Goroutine, for a few nanoseconds copies the value of "NUM_PROCESSED" in memory to increment it and then update the variable.

If two (or more) Goroutines copy the value around the same time, they will not be aware of updates made by other Goroutines.

For example: let's assume the value was "200"; each of the two Goroutines will add "1" i.e. "201". Thus the value for "NUM_PROCESSED" will become "201" instead of "202".

This is where a Mutex comes in handy. A Mutex will create a "lock" in the process - so that only one Goroutine can update "NUM_PROCESSED" at a time.

Goroutines

The other Goroutines will then be paused until the lock is released. This is very similar to a queue - when each Goroutine releases the lock, the next one acquires a new lock and the process continues until all the queued Goroutines finish updating "NUM_PROCESSED".

A Mutex example

We can modify the above code to introduce a Mutex as follows:

package main

import (
    "fmt"
    "sync"
)

var NUM_PROCESSED = 0
var MUTEX sync.Mutex

func countNumProcessed(wg *sync.WaitGroup) {
    defer wg.Done()
    MUTEX.Lock()
    NUM_PROCESSED++
    MUTEX.Unlock()
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 500; i++ {
        wg.Add(1)
        go countNumProcessed(&wg)
    }

    wg.Wait()
    fmt.Println(NUM_PROCESSED)
}

You will notice with the above code, that no matter how many times you run this function, it will always print "500". This is precisely the same amount of goroutines created in the for loop.