Mutex in Golang

Mutexes are an important topic in concurrency. They appear in almost every programming language when they talk about concurrency. In this post, we are going to look at what problems mutexes solve and how.

The race condition

The race condition appears when multiple goroutines try to access and update the shared data. They instead fail to update data properly and produce incorrect output. This condition is called race condition and happens due to repeated thread access.

Here is a simple program to illustrate the problem.

package main

import (
	"fmt"
	"sync"
)

func f(v *int, wg *sync.WaitGroup) {
	*v++
	wg.Done()
}

func main() {

	var wg sync.WaitGroup
	var v int = 0

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go f(&v, &wg)
	}

	wg.Wait()
	fmt.Println("Finished", v)
}
Go Race Condition
Go Race Condition

It is clear that the program is outputting the wrong result each time we run it. Here race condition is occurring. So, let’s try to rewrite this program with a mutex.

Working of a mutex in Golang

Before we write that program, let’s see what is mutex actually? A mutex is simply a mutual exclusion in short. It means that when multiple threads access and modify shared data before they do that they need to acquire a lock. Then when the work is done they release the lock and let some other goroutine to acquire the lock.

This allows the goroutines to have synchronized access to the data and prevents data race.

// working of a mutex:

// acquire lock
// modify data
// release lock

How to use the mutex in Go

Now, we will modify the above program so that the data race not occur. We will use a mutex to stop that. Here is an approach on how to go about it.

package main

import (
	"fmt"
	"sync"
)

func f(v *int, wg *sync.WaitGroup, m *sync.Mutex) {
	// acquire lock
	m.Lock()
	// do operation
	*v++
	// release lock
	m.Unlock()
	wg.Done()
}

func main() {

	var wg sync.WaitGroup
	// declare mutex
	var m sync.Mutex
	var v int = 0

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go f(&v, &wg, &m)
	}

	wg.Wait()
	fmt.Println("Finished", v)
}
Go Mutex Example
Go Mutex Example

In this example after adding mutex, the program outputs correctly.

Mutex vs atomic package

It may be confusing that mutexes are very similar to atomic operations but they are much more complicated than that. Atomics utilize CPU instructions whereas mutexes utilize the locking mechanism. So when updating shared variables like integers, atomics are faster. But the real power of mutexes comes when the complex structure of data is handled concurrently. Then it is the only option since atomics don’t support that.