62 lines
2.7 KiB
Markdown
62 lines
2.7 KiB
Markdown
# Concurrency Patterns — Derived Rules
|
|
|
|
> Derives from `domains/concurrency/first-principles.md`. Common concurrency patterns and when to use them.
|
|
|
|
## Pattern 1: Message Passing (P5 Lock Minimization)
|
|
|
|
- Threads/goroutines communicate via channels/queues, not shared memory.
|
|
- "Don't communicate by sharing memory; share memory by communicating." (Go proverb)
|
|
- Use when the data flows naturally in one direction. Avoids locks entirely.
|
|
|
|
## Pattern 2: Read-Write Lock (P5 Lock Minimization)
|
|
|
|
- Multiple readers, one writer. A `RwLock` allows concurrent reads, exclusive writes.
|
|
- Use when reads vastly outnumber writes (e.g., a config cache).
|
|
- Avoid when writes are frequent — the lock degrades to a mutex.
|
|
|
|
## Pattern 3: Actor Model (P2 Single Responsibility)
|
|
|
|
- Each actor owns its state. Actors communicate via messages. No shared state.
|
|
- Use for isolated, long-lived workers (e.g., a session handler, a chat room).
|
|
- Erlang/Akka/Pony are built on this. Implementable in any language with channels.
|
|
|
|
## Pattern 4: Immutable Data Structures (P1 Immutability by Default)
|
|
|
|
- Data is never mutated; a "change" creates a new value. Old values are safe to share.
|
|
- Use in functional languages (Haskell, Clojure) or via persistent data structures (Immer.js).
|
|
- Eliminates entire classes of races. The trade-off is allocation cost.
|
|
|
|
## Pattern 5: Bounded Queue with Backpressure (P9 Bounded Queues)
|
|
|
|
- A queue with a max size. When full, the producer is blocked or signaled.
|
|
- Use to bound memory and propagate slowness from consumer to producer.
|
|
- An unbounded queue hides a slow consumer until OOM. Always bound.
|
|
|
|
## Pattern 6: Timeout on Every Block (P8 Timeout Discipline)
|
|
|
|
- Every blocking call (lock acquire, queue send, HTTP request) has a timeout.
|
|
- Use a timeout, not a forever-block. Forever is not a duration.
|
|
- On timeout: cancel, retry, or fail. Do not hang.
|
|
|
|
## Pattern 7: Cancellation Propagation (P7 Cancellation Support)
|
|
|
|
- A cancellation signal propagates to all spawned work. Cancel the parent, the children stop.
|
|
- Use context (`context.Context` in Go, `AbortController` in JS, `CancellationToken` in C#).
|
|
- Cancellation is fast and complete. No orphaned goroutines/threads.
|
|
|
|
## Pattern 8: Lock-Free Where Possible (P5)
|
|
|
|
- Atomic operations (compare-and-swap) for simple state. No lock.
|
|
- Use for counters, flags, simple pointers.
|
|
- Avoid for complex state — lock-free code is subtle and easy to get wrong.
|
|
|
|
## What Violates Concurrency Patterns
|
|
|
|
| Violation | Pattern |
|
|
|-----------|---------|
|
|
| Shared mutable state with no lock | (race, P1) |
|
|
| Unbounded queue | P5 (OOM) |
|
|
| `channel.send()` with no timeout | P6 (hang) |
|
|
| Spawned goroutine with no cancellation | P7 (orphan) |
|
|
| A mutex held across an I/O call | P3 (lock scope) |
|
|
| `sync.Mutex` for a counter | P8 (use atomic) | |