d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
4.8 KiB
4.8 KiB
Thread Pool Pattern
Problem
Creating a new thread for every task is expensive (memory allocation, OS scheduling overhead). Under high load, unbounded thread creation can exhaust system resources.
Solution
Pre-allocate a fixed number of worker threads. Tasks are submitted to a queue, and idle workers pick up tasks. This bounds resource usage and amortizes thread creation cost across many tasks.
When to Use
- Server request handling with bounded concurrency
- Batch processing with many small, independent tasks
- CPU-bound work that benefits from parallelism up to core count
- Limiting concurrent access to a shared resource
When to Avoid
- Tasks that are long-lived or block indefinitely (starves other tasks)
- When task count is very small (overhead of pool management not justified)
- Real-time systems where thread scheduling latency is critical
Pseudocode
pool = ThreadPool(workers=4)
function task(id):
print("Task " + id + " running on " + current_thread())
sleep(random)
print("Task " + id + " done")
for i in 1..10:
pool.submit(task, i)
pool.shutdown()
print("All tasks complete")
Python
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading
import time
import random
def task(task_id: int) -> str:
thread_name = threading.current_thread().name
print(f"[Task {task_id}] Started on {thread_name}")
duration = random.uniform(0.1, 0.3)
time.sleep(duration)
result = f"Task {task_id} completed in {duration:.2f}s"
print(f"[Task {task_id}] Finished")
return result
def main() -> None:
with ThreadPoolExecutor(max_workers=4, thread_name_prefix="Worker") as pool:
futures = {pool.submit(task, i): i for i in range(10)}
for future in as_completed(futures):
task_id = futures[future]
result = future.result()
print(f" Result: {result}")
print("All tasks complete.")
if __name__ == "__main__":
main()
Go
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func worker(id int, tasks <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for taskID := range tasks {
fmt.Printf("[Worker %d] Processing task %d\n", id, taskID)
duration := time.Duration(100+rand.Intn(200)) * time.Millisecond
time.Sleep(duration)
fmt.Printf("[Worker %d] Finished task %d in %v\n", id, taskID, duration)
}
}
func main() {
const numWorkers = 4
const numTasks = 10
tasks := make(chan int, numTasks)
var wg sync.WaitGroup
// Start workers
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(i, tasks, &wg)
}
// Submit tasks
for i := 0; i < numTasks; i++ {
tasks <- i
}
close(tasks)
wg.Wait()
fmt.Println("All tasks complete.")
}
JavaScript
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
class WorkerPool {
constructor(concurrency) {
this.concurrency = concurrency;
this.queue = [];
this.active = 0;
this.resolveIdle = null;
}
submit(taskFn) {
return new Promise((resolve, reject) => {
this.queue.push({ taskFn, resolve, reject });
this._tryRun();
});
}
_tryRun() {
while (this.active < this.concurrency && this.queue.length > 0) {
const { taskFn, resolve, reject } = this.queue.shift();
this.active++;
taskFn()
.then(resolve)
.catch(reject)
.finally(() => {
this.active--;
this._tryRun();
if (this.active === 0 && this.queue.length === 0 && this.resolveIdle) {
this.resolveIdle();
}
});
}
}
async waitIdle() {
if (this.active === 0 && this.queue.length === 0) return;
return new Promise((r) => (this.resolveIdle = r));
}
}
async function task(id) {
const worker = `Worker-${id % 4}`;
console.log(`[${worker}] Processing task ${id}`);
const duration = 100 + Math.random() * 200;
await sleep(duration);
console.log(`[${worker}] Finished task ${id} in ${duration.toFixed(0)}ms`);
return `Task ${id} done`;
}
async function main() {
const pool = new WorkerPool(4);
const results = [];
for (let i = 0; i < 10; i++) {
results.push(pool.submit(() => task(i)));
}
const allResults = await Promise.all(results);
allResults.forEach((r) => console.log(` Result: ${r}`));
console.log("All tasks complete.");
}
main();
Related Patterns
- Producer-Consumer — the thread pool workers are the consumers; tasks submitted to the pool are produced by the caller.
- Object Pool — a thread pool is a specialised object pool where the pooled resource is a thread.
- Future/Promise — task submission to a thread pool typically returns a future representing the eventual result.
- Bulkhead — a fixed-size thread pool implements a bulkhead, isolating resource consumption per dependency.