Help us improve
Share bugs, ideas, or general feedback.
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-brigidHow this skill is triggered — by the user, by Claude, or both
Slash command
/dt-brigid:concurrency-model-selectionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
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.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
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.
Guides systematic root-cause debugging when tests fail, builds break, or unexpected errors occur. Provides a structured triage checklist to preserve evidence, localize, and fix issues instead of guessing.
Share bugs, ideas, or general feedback.
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.