Back to All Concepts
ConcurrencyPatternsLow LevelGoJavaAdvanced

Concurrency Patterns

Essential patterns for managing concurrent execution. Mutex, Semaphores, Monitors, and modern approaches like the Actor Model and CSP.

Why Concurrency is Hard

Concurrency introduces non-determinism. Without proper synchronization, you get race conditions, deadlocks, and starvation.

This guide covers the fundamental patterns used to serialize access to shared resources.

1. Mutex (Mutual Exclusion)

The simplest synchronization primitive. Only one thread can hold the lock at a time.

Use Case: Protecting a shared counter or critical section.

go
package main

import (
	"sync"
)

type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()   // Lock before accessing map
	defer c.mu.Unlock() // Ensure unlock happens even if panic
	c.v[key]++
}
Click to expand code...

[!WARNING] Forget to unlock? Deadlock. Using defer in Go or try-finally in Java/Python is critical.

2. Semaphore

A mutex that allows NN concurrent accesses.

Use Case: Limiting connections to a database or rate limiting.

mermaid
graph TD
    Queue[Threads Waiting] -->|Acquire| Sem{Semaphore N=3}
    Sem -->|Permit 1| Res1[Resource 1]
    Sem -->|Permit 2| Res2[Resource 2]
    Sem -->|Permit 3| Res3[Resource 3]
    Res1 -->|Release| Sem
Click to expand code...

3. Read-Write Lock (RWMutex)

Allows multiple readers OR one writer.

Use Case: Caches where reads vastly outnumber writes.

  • RLock(): Blocks if a writer holds lock. Allows other readers.
  • Lock(): Blocks until all readers finish. Exclusive access.

4. Monitor Pattern

Combines a Mutex with Condition Variables (Wait/Notify).

Pattern:

  1. Acquire Lock.
  2. Check Condition. If false, Wait (release lock, sleep).
  3. Do work.
  4. Notify waiting threads.
  5. Release Lock.

Java Example (Synchronized):

java
public synchronized void produce(Item item) {
    while (queue.isFull()) {
        wait(); // Releases lock and sleeps
    }
    queue.add(item);
    notifyAll(); // Wakes up consumers
}
Click to expand code...

5. Communicating Sequential Processes (CSP)

Popularized by Go. "Do not communicate by sharing memory; share memory by communicating."

Key Idea: Use Channels to pass data between Goroutines. No explicit locks.

go
package main

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Start 3 workers
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // Send jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
}

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        results <- j * 2
    }
}
Click to expand code...

6. Actor Model

Popularized by Erlang and Akka (Scala/Java).

Key Concepts:

  • Actor: Fundamental unit of computation.
  • Mailbox: Actors communicate only by sending messages.
  • State: Private, never shared. Modified only by processing messages.
  • No Locks: Messages are processed sequentially.
mermaid
sequenceDiagram
    participant A as Actor A
    participant B as Actor B
    
    A->>B: Send Message "Compute(5)"
    Note right of B: Add to Mailbox
    B->>B: Process "Compute(5)"
    B->>A: Send Result "25"
Click to expand code...

Comparison

PatternShared State?Locking?Best For
MutexYesExplicitFine-grained state protection
CSP (Channels)NoImplicitStream processing, Pipelines
Actor ModelNoNoneDistributed systems, Fault tolerance
AtomicsYesHardwareCounters, Flags (High Perf)

Related Concepts

About ScaleWiki

ScaleWiki is an interactive educational platform dedicated to demystifying distributed systems, software architecture, and system design. Our mission is to provide high-quality, technically accurate resources for software engineers preparing for interviews or solving complex scaling challenges in production.

Read more about our Editorial Guidelines & Authorship.

Educational Disclaimer: The architectural patterns and system designs discussed in this article are based on common industry practices, technical whitepapers, and public engineering blogs. Actual implementations in enterprise environments may vary significantly based on specific product requirements, legacy constraints, and evolving technologies.

Related Articles