Menu
Coddy logo textTech

sync.Mutex & sync.RWMutex

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 63 of 107.

While channels are Go's preferred way to coordinate goroutines, sometimes you need to protect shared data directly. The sync package provides mutexes—locks that ensure only one goroutine accesses a resource at a time.

A sync.Mutex has two methods: Lock() and Unlock(). When a goroutine calls Lock(), it gains exclusive access. Other goroutines calling Lock() will block until Unlock() is called:

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

Using defer c.mu.Unlock() ensures the lock is released even if the function returns early or panics—a common and recommended pattern.

When reads are frequent but writes are rare, sync.RWMutex offers better performance. It allows multiple readers simultaneously, but writers get exclusive access:

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // multiple readers allowed
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // exclusive access for writing
    defer c.mu.Unlock()
    c.data[key] = value
}

Use RLock()/RUnlock() for read operations and Lock()/Unlock() for writes. This allows concurrent reads while ensuring writes are safe.

challenge icon

Challenge

Easy

Let's build a thread-safe inventory system that tracks product stock levels. Your system will handle concurrent reads and writes safely using mutexes, ensuring data integrity when multiple operations happen simultaneously.

You'll organize your code across two files:

  • inventory.go: Define your thread-safe inventory management system.

    Create an Inventory struct that stores product quantities in a map and uses an sync.RWMutex to protect access. Your inventory should support these operations:

    • NewInventory() *Inventory - Creates a new inventory with an initialized map
    • AddStock(product string, quantity int) - Adds quantity to a product's stock (use exclusive lock since this modifies data)
    • GetStock(product string) int - Returns the current stock for a product, or 0 if not found (use read lock since this only reads data)
    • RemoveStock(product string, quantity int) bool - Removes quantity from stock if sufficient stock exists. Returns true if successful, false if insufficient stock (use exclusive lock)

    Remember to use defer for unlocking to ensure locks are always released properly.

  • main.go: Read operations and demonstrate your thread-safe inventory.

    Read the number of operations, then process each operation. Each operation has a type (add, get, or remove), a product name, and for add/remove operations, a quantity.

    For each operation, print the result:

    • add: Print Added [quantity] [product]
    • get: Print [product]: [stock] in stock
    • remove: Print Removed [quantity] [product] if successful, or Insufficient stock for [product] if not

The following inputs will be provided:

  • Line 1: Number of operations (integer)
  • Following lines: For each operation:
    • Operation type (add, get, or remove)
    • Product name
    • Quantity (only for add and remove operations)

For example, given:

5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
apples

Your output should be:

Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stock

The RWMutex allows multiple GetStock calls to read simultaneously, while AddStock and RemoveStock get exclusive access when modifying the inventory.

Cheat sheet

The sync package provides mutexes to protect shared data when multiple goroutines need direct access to the same resource.

sync.Mutex

A sync.Mutex provides exclusive access using Lock() and Unlock():

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

Using defer with Unlock() ensures the lock is released even if the function returns early or panics.

sync.RWMutex

A sync.RWMutex allows multiple concurrent readers but exclusive access for writers:

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // multiple readers allowed
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // exclusive access for writing
    defer c.mu.Unlock()
    c.data[key] = value
}

Use RLock()/RUnlock() for read operations and Lock()/Unlock() for write operations.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Read number of operations
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Create a new inventory
	inventory := NewInventory()
	
	// Process each operation
	for i := 0; i < numOps; i++ {
		// Read operation type
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		// Read product name
		productLine, _ := reader.ReadString('\n')
		product := strings.TrimSpace(productLine)
		
		// TODO: Handle each operation type (add, get, remove)
		// For "add" and "remove", read the quantity from input
		// Call the appropriate inventory method
		// Print the result according to the challenge description
		
		switch opType {
		case "add":
			// TODO: Read quantity, add stock, print result
			
		case "get":
			// TODO: Get stock, print result
			
		case "remove":
			// TODO: Read quantity, remove stock, print appropriate result
			
		}
	}
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming