What is mutex? How does it prevent race conditions?

Kerala Blockchain Academy
3 min readOct 4, 2024

--

By Mobin Mohanan, R &D Engineer (Ethereum), Kerala Blockchain Academy

Mutual Exclusion, or simply “mutex”, is a concurrent programming feature to prevent race conditions.

A race condition occurs when two or more threads access shared data simultaneously, and at least one of the accesses is a write.

By locking the resource, a mutex ensures that only one thread can modify the resource at a time, thereby preventing data inconsistencies and ensuring thread safety.

Let’s write an example in Go.

Go uses “goroutines”, lightweight threads for concurrency.

We can write a goroutine by adding the “go” keyword before a function.

go f(x, y, z) {}

A “WaitGroup” waits for a collection of goroutines to finish. The main goroutine calls “Add” to set the number of goroutines to wait for. Then each of the goroutines runs and calls “Done” when finished. At the same time, “Wait” can be used to block until all goroutines have finished.

var wg sync.WaitGroup

wg.Add(int)
wg.Done()
wg.Wait()

First, we will create a faulty program without mutex displaying the need for it.

package main

import (
"fmt"
"sync"
"time"
)

func main() {
counter := 0
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
time.Sleep(time.Nanosecond)
counter++
}
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}

The main function initialises a counter and a WaitGroup, then starts 10 goroutines. Each goroutine increments the counter 10 times, with a brief sleep between increments.

Theoretically, this program should print 100 as the counter value nonetheless. But sometimes, it won’t. (Try it yourself.)

That’s because some increments overlap, while one succeeds and the others fail, just like a “race”.

This issue increases “exponentially” if we increase the loop range or the number of goroutines.

This is where mutex comes in. In Go, it’s straightforward to implement mutex.

Declare a “Mutex” variable; the “Lock” method is used to acquire the mutex, blocking other goroutines from accessing the locked section until the mutex is released. The “Unlock” method releases the mutex, allowing other waiting goroutines to proceed.

var mu sync.Mutex

mu.Lock()
mu.Unlock()

Let’s rewrite our previous example and try it out.

package main

import (
"fmt"
"sync"
"time"
)

func main() {
counter := 0
var wg sync.WaitGroup
var mu sync.Mutex

for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
time.Sleep(time.Nanosecond)
mu.Lock()
counter++
mu.Unlock()
}
}()
}

wg.Wait()
fmt.Println("Counter:", counter)
}

See the difference?

Understanding the use of concurrency tools like goroutines, WaitGroups, and mutexes in Go is essential for writing efficient and safe concurrent programs.

By leveraging these mechanisms, you can manage multiple tasks simultaneously while preventing race conditions and ensuring proper synchronisation.

Mastering these concepts will enhance your ability to build robust and performant applications as you delve deeper into programming.

--

--

Kerala Blockchain Academy
Kerala Blockchain Academy

Written by Kerala Blockchain Academy

One-stop solution for quality blockchain education and research. Offers best in class blockchain certification programs in multiple blockchain domains.

Responses (1)