From dt-brigid
Concurrency model decision framework — CSP vs actors vs async/await vs structured concurrency, by problem characteristics and language
npx claudepluginhub dreamteam-hq/brigid --plugin dt-brigidThis skill uses the workspace's default tool permissions.
This skill provides a decision framework for choosing the right concurrency model given problem characteristics, language constraints, and operational requirements. It is not an encyclopedia — it focuses on the decision boundaries where picking the wrong model causes real pain.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
This skill provides a decision framework for choosing the right concurrency model given problem characteristics, language constraints, and operational requirements. It is not an encyclopedia — it focuses on the decision boundaries where picking the wrong model causes real pain.
Sequential processes that communicate exclusively through typed channels. No shared memory. Composition through channel wiring.
Core primitives: channels (buffered/unbuffered), select/alt, goroutines or similar lightweight processes.
Strengths:
Weaknesses:
Where it lives: Go (native), Clojure core.async, Crystal, Kotlin channels, Rust crossbeam-channel.
Isolated processes with private state, communicating through asynchronous message passing. Each actor processes one message at a time.
Core primitives: actors/processes, mailboxes, supervision trees, location transparency.
Strengths:
Weaknesses:
Where it lives: Erlang/OTP (native), Akka (JVM), Microsoft Orleans (.NET), Elixir, Pony.
Suspendable computations managed by an event loop or executor. The programmer writes sequential-looking code that yields at I/O boundaries.
Core primitives: futures/promises, async functions, executors/runtimes, cancellation tokens.
Strengths:
Weaknesses:
Pin<Box<dyn Future>>) add complexity in systems languagesWhere it lives: JavaScript/TypeScript (native), Python asyncio, C# Task/ValueTask, Rust async (tokio/async-std), Kotlin coroutines, Swift concurrency.
Child tasks are scope-bound to their parent. When a scope exits, all child tasks are joined or cancelled. No fire-and-forget.
Core primitives: task groups, nurseries/scopes, cancellation scopes, error propagation.
Strengths:
Weaknesses:
Where it lives: Java 21+ (virtual threads + StructuredTaskScope), Kotlin coroutineScope, Swift TaskGroup, Python trio/anyio, Rust async (emerging patterns).
The same operation applied to many data elements simultaneously. Exploits hardware parallelism (SIMD, GPU, multi-core) with minimal coordination.
Core primitives: parallel iterators, SIMD intrinsics, GPU kernels, vectorized operations.
Strengths:
Weaknesses:
Where it lives: Rust Rayon, Java parallel streams, .NET PLINQ, Python multiprocessing/NumPy, CUDA/Vulkan compute, Go errgroup (coarse-grained).
| Problem Characteristic | Best Fit | Avoid |
|---|---|---|
| I/O-bound, many connections (HTTP servers, proxies) | Async/await | Data parallelism |
| CPU-bound, independent chunks (image processing, batch transforms) | Data parallelism | Actors (overhead) |
| Stateful entities with identity (user sessions, game entities, IoT devices) | Actors | CSP (no natural entity mapping) |
| Pipeline with stages (ETL, stream processing) | CSP | Actors (over-engineered) |
| Fault tolerance is primary concern (telecom, payment processing) | Actors (with supervision) | Raw async/await |
| Short-lived concurrent subtasks (parallel API calls, scatter-gather) | Structured concurrency | Actors (lifecycle overhead) |
| Mixed I/O and CPU (web app with compute-heavy endpoints) | Async/await + data parallelism hybrid | Single model forced everywhere |
| Shared mutable state, high contention (concurrent caches, counters) | Locks/atomics directly | CSP (serialization bottleneck) |
| Request/response with timeouts (microservice calls) | Async/await + structured concurrency | Actors (ask pattern complexity) |
| Long-running background workflows (batch jobs, cron-like) | CSP or actors | Structured concurrency (scope mismatch) |
Primary model: CSP (goroutines + channels). It is the language's native concurrency model and the ecosystem is built around it.
When to reach beyond CSP:
sync.Mutex, sync.Map, or atomic directly rather than serializing through a channel.errgroup for coarse-grained; for fine-grained, consider calling into C/Rust via CGo or using specialized libraries.errgroup.Group with context cancellation provides scope-bound task management.Primary model: Async/await (tokio) for I/O-heavy; Rayon for CPU-heavy. The ownership system makes shared-state concurrency safer than in other languages.
When to reach beyond async/await:
tokio::spawn_blocking (which wastes the async runtime's blocking pool).actix exists but the actor pattern is less natural in Rust due to ownership constraints on message types. Consider channels (crossbeam, tokio::mpsc) instead.tokio::JoinSet provides scope-bound task management.Primary model: Async/await with Task/ValueTask. The runtime, frameworks, and ecosystem assume this model.
When to reach beyond async/await:
Parallel.ForEachAsync, PLINQ, or System.Numerics.Vector<T> for SIMD.Task.WhenAll + CancellationTokenSource linking.Primary model: asyncio for I/O-bound; multiprocessing for CPU-bound (GIL bypass). The GIL means threading is not useful for CPU parallelism.
When to reach beyond asyncio:
multiprocessing, concurrent.futures.ProcessPoolExecutor, or NumPy/Pandas vectorized operations.trio or anyio for scope-bound task management. Prefer these over raw asyncio.gather.Primary model: Async/await with Promises. Single-threaded event loop; concurrency is cooperative by design.
When to reach beyond async/await:
Promise.allSettled + AbortController provides partial structured semantics. No native scope-bound tasks.Primary model: Virtual threads (Loom) + structured concurrency for new code. Thread-per-request is viable again with virtual threads.
When to reach beyond virtual threads:
ForkJoinPool for recursive decomposition.Primary model: Cooperative multitasking via signals and await. Godot's scene tree is single-threaded by default.
When to reach beyond signals/await:
WorkerThreadPool for physics, pathfinding, terrain generation. Keep rendering on the main thread.| Model | Typical Bug | Mitigation |
|---|---|---|
| CSP | Goroutine leak (blocked on channel nobody reads) | Context cancellation, bounded channels, leak detectors (goleak) |
| Actors | Mailbox overflow under load | Back-pressure protocols, bounded mailboxes, load shedding |
| Async/await | Executor starvation from blocking call | Dedicated blocking thread pool, lint rules against sync I/O in async |
| Structured concurrency | Scope too narrow (task cancelled prematurely) | Design scopes around logical operations, not syntactic blocks |
| Data parallelism | False sharing on cache lines | Padding, per-thread accumulators, reduction patterns |
The five models above are not exhaustive. Sometimes the right answer is a mutex, a read-write lock, or an atomic counter:
Mutex<HashMap> in Rust or sync.RWMutex in Go is simpler and faster than a channel or actor for a config cache read 1000x per second and written once per minute.AtomicU64, atomic.Int64) for metrics, feature flags, reference counts. No model overhead needed.cross-language-interop, roslyn-analyzers for .NET, rust-project-patterns) for implementation details beyond concurrency.