0
0
GoHow-ToBeginner · 3 min read

How to Use sync.RWMutex in Go for Safe Concurrent Access

In Go, use sync.RWMutex to protect shared data by allowing multiple readers or one writer at a time. Use RLock() and RUnlock() for reading, and Lock() and Unlock() for writing to ensure safe concurrent access.
📐

Syntax

The sync.RWMutex type provides two kinds of locks: read locks and write locks.

  • Lock(): Acquires a write lock. Only one goroutine can hold it, blocking others.
  • Unlock(): Releases the write lock.
  • RLock(): Acquires a read lock. Multiple goroutines can hold it simultaneously.
  • RUnlock(): Releases the read lock.

Use write locks when modifying data, and read locks when only reading data.

go
var mu sync.RWMutex

// For writing:
mu.Lock()
// modify shared data
mu.Unlock()

// For reading:
mu.RLock()
// read shared data
mu.RUnlock()
💻

Example

This example shows a shared counter accessed by multiple goroutines. Readers use RLock() to read the value concurrently, while writers use Lock() to update it exclusively.

go
package main

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

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

	// Writer goroutine
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < 3; i++ {
			mu.Lock()
			counter++
			fmt.Println("Writer incremented counter to", counter)
			mu.Unlock()
			time.Sleep(100 * time.Millisecond)
		}
	}()

	// Reader goroutines
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < 3; j++ {
				mu.RLock()
				fmt.Printf("Reader %d reads counter: %d\n", id, counter)
				mu.RUnlock()
				time.Sleep(50 * time.Millisecond)
			}
		}(i + 1)
	}

	wg.Wait()
}
Output
Writer incremented counter to 1 Reader 1 reads counter: 1 Reader 2 reads counter: 1 Reader 3 reads counter: 1 Reader 1 reads counter: 1 Writer incremented counter to 2 Reader 2 reads counter: 2 Reader 3 reads counter: 2 Reader 1 reads counter: 2 Reader 2 reads counter: 2 Writer incremented counter to 3 Reader 3 reads counter: 3 Reader 1 reads counter: 3 Reader 2 reads counter: 3 Reader 3 reads counter: 3
⚠️

Common Pitfalls

Common mistakes when using sync.RWMutex include:

  • Forgetting to unlock after locking, causing deadlocks.
  • Using Lock() when only reading, which reduces concurrency.
  • Using RLock() when writing, which can cause data races.
  • Holding locks for too long, blocking other goroutines.

Always pair Lock() with Unlock() and RLock() with RUnlock() in the same function or scope.

go
package main

import (
	"sync"
)

func main() {
	var mu sync.RWMutex
	shared := 0

	// Wrong: Using RLock for writing (causes data race)
	mu.RLock()
	shared = 1 // unsafe write
	mu.RUnlock()

	// Correct way:
	mu.Lock()
	shared = 1
	mu.Unlock()
}
📊

Quick Reference

Remember these tips when using sync.RWMutex:

  • Use RLock()/RUnlock() for reading to allow multiple readers.
  • Use Lock()/Unlock() for writing to ensure exclusive access.
  • Never mix read and write locks incorrectly.
  • Always unlock in the same function to avoid deadlocks.
  • Keep lock scope as small as possible for better performance.

Key Takeaways

Use sync.RWMutex to allow multiple concurrent readers or one exclusive writer.
Always pair Lock with Unlock and RLock with RUnlock to avoid deadlocks.
Use RLock for reading and Lock for writing to protect shared data safely.
Avoid holding locks longer than necessary to keep your program responsive.
Incorrect use of locks can cause data races or deadlocks, so be careful.