Concurrency Design Patterns
Patterns for multi-threaded programming: Producer-Consumer, Reader-Writer, Thread Pool, Future/Promise, Reactor, Monitor, Scheduler.
Patterns for multi-threaded programming: Producer-Consumer, Reader-Writer, Thread Pool, Future/Promise, Reactor, Monitor, Scheduler.
| Name | Problem | Solution brief | Key components | Benefits | Pitfalls | Language examples |
|---|---|---|---|---|---|---|
| Producer-Consumer (Bounded Buffer) | Producers generate data; consumers process it. Need to coordinate so consumers don't read from empty buffer, producers don't write to full buffer. | Use a fixed-size queue as buffer. Producers block when full; consumers block when empty. Synchronized access ensures thread safety. |
| Decouples producers and consumers, smooths load variations, enables pipelining, buffers spikes in production/consumption rates. | Deadlock if synchronization incorrect, priority inversion possible, buffer size tuning critical (too small causes blocking; too large uses memory), consumers may starve if producers fast. |
|
| Reader-Writer Lock | Data structure frequently read, rarely written. Mutex allows only one thread at a time (even for readers), causing unnecessary contention. | Allow multiple readers simultaneous access, but writers get exclusive access. Readers don't block each other; writers block both readers and other writers. |
| Better throughput for read-heavy workloads, scales well with many readers, writers get exclusive access to prevent dirty reads. | Writer starvation if continuous readers arrive, reader-writer deadlock if not careful, higher overhead than simple mutex for write-heavy cases, complex to implement correctly. |
|
| Thread Pool | Creating/destroying threads is expensive (OS resources, context switching). Spawning thread per task leads to overhead and uncontrolled resource usage. | Pre-create a fixed number of worker threads and queue tasks. Workers wait for tasks; when task arrives, idle worker executes it. Reuses threads across many tasks. |
| Reduces thread creation overhead, bounds resource usage, improves response time (threads ready), enables task prioritization and scheduling. | Queue can become bottleneck, thread starvation if pool too small, deadlocks if tasks wait on other queued tasks, tasks may block pool threads indefinitely. |
|
| Future / Promise | Need to run computation asynchronously and retrieve result later. Caller shouldn't block waiting for result; needs way to check completion or get result when ready. | Future is a placeholder for result of async operation. Promise is writeable handle that fulfills the future. Caller gets future immediately, checks isDone(), calls get() to block or poll. |
| Avoids blocking threads, enables composition of async operations, simplifies error propagation, clean API for async results. | Blocking get() defeats purpose (use callbacks instead), exceptions must be captured in Future, cancellation not always supported, many small async tasks create overhead. |
|
| Reactor Pattern | Server handling many simultaneous connections (e.g., web server). One thread per connection (thread-per-connection) doesn't scale (thousands of connections = thousands of threads). | Single (or few) thread(s) use non-blocking I/O and demultiplexer (select/poll/epoll/kqueue) to monitor multiple sockets. When I/O event occurs, dispatcher calls appropriate handler callback. |
| Scales to thousands of connections with few threads, efficient resource usage (no thread overhead), good for I/O-bound services. | Complex to implement correctly, callbacks can lead to spaghetti code (use coroutines/fibers), CPU-bound tasks still block reactor, edge-triggered vs level-triggered pitfalls. |
|
| Monitor (Monitor Object) | Multiple threads access shared data; need to ensure mutual exclusion and coordinate access. Condition variables and mutexes scattered lead to error-prone code. | Encapsulate shared data with its synchronization (mutex) and condition variables inside a monitor object. Only one thread can execute any monitor method at a time. Condition variables allow threads to wait for conditions. |
| Simplifies synchronization (acquire/release implicit), prevents unsynchronized access (all accesses go through monitor), high-level abstraction, reduces race conditions. | Can cause performance bottleneck if monitor too large (coarse-grained), conditional waiting can lead to missed signals or spurious wakeups, deadlocks possible if monitor calls external code. |
|
| Scheduler Pattern | Need to execute tasks at specific times (cron jobs), at fixed intervals, or after delays. Managing timers and thread pools manually is complex. | Central scheduler coordinates task execution based on time criteria. Maintains priority queue of scheduled tasks; scheduler thread manages timers and dispatches tasks to worker threads when due. |
| Centralized task management, efficient timer management (single thread for many timers), supports recurring tasks, cron-like scheduling, easy to pause/cancel/reschedule. | Scheduler thread single point of failure, task overruns can backlog, clock drift affects timing, distributed systems need distributed scheduler (e.g., Quartz, Celery). |
|
The static files are identical to what the API returns, but with no rate limit and no server round trip. Use the API when you want a query and a content type; use the files when you want to cache one document.
curl "https://yjtoon.com/api/dataset/concurrency-design-patterns?format=toon"
const res = await fetch( "https://yjtoon.com/static-data/dataset/concurrency-design-patterns.toon" ); const toon = await res.text();
Rate limit: 120 requests per minute per IP, no key and no signup. API reference →