m07-concurrency
actionbook/rust-skills
Master Rust concurrency: threads, async/await, channels, and thread-safe primitives.
What is m07-concurrency?
Diagnoses concurrency errors (Send/Sync, deadlocks, race conditions) and guides you to the right primitive—threads for CPU work, async for I/O, channels for message passing, or Mutex/RwLock for shared mutable state. Use when you see E0277, thread/spawn/async errors, or need to design concurrent systems.
- Identifies whether workload is CPU-bound (threads), I/O-bound (async), or mixed
- Explains Send/Sync marker traits and when types can cross thread boundaries
- Recommends correct primitive: std::thread, tokio, channels, Arc, Mutex, RwLock, or Atomic types
- Detects anti-patterns: holding locks across await, Arc<Mutex<T>> overuse, thread::sleep in async
- Traces errors up to domain context (web, fintech, CLI) to find root design issues
- Provides decision flowchart for choosing between threads, async, message passing, and shared-state patterns
How to install m07-concurrency
npx skills add https://github.com/actionbook/rust-skills --skill m07-concurrencyHow to use m07-concurrency
- 1.Identify the error type (E0277 Send/Sync, deadlock, future not Send, etc.) or concurrency design question
- 2.Ask the core question: Is this CPU-bound or I/O-bound, and what data needs sharing?
- 3.Trace up to domain context if applicable (web API, fintech, CLI) using the Domain Detection Table
- 4.Use the decision flowchart to select the right primitive: threads, async, channels, Arc, Mutex, RwLock, or Atomic
- 5.Apply the pattern or fix from the Common Errors or Anti-Patterns table
- 6.Verify Send/Sync bounds are satisfied and locks are scoped tightly (not held across await)
Use cases
- Fix E0277 'Send not satisfied' by determining if type should cross threads or use spawn_local instead
- Design web API state management: replace Rc with Arc<T> in handlers that run on any thread
- Choose between Mutex and channels: Mutex for shared mutable state, channels for message passing
- Prevent deadlocks by enforcing consistent lock ordering and scoping guards tightly
- Handle non-Send types in async: drop before await, use spawn_local, or switch to Arc
- Rust developers hitting Send/Sync compiler errors
- Backend engineers building concurrent web services or APIs
- Systems programmers parallelizing CPU-bound work
- Anyone designing multi-threaded or async Rust applications
m07-concurrency FAQ
Use std::thread or rayon for CPU-bound work (parallelism). Use async/await with tokio for I/O-bound work (concurrency). Use spawn_blocking in async contexts to run blocking CPU code without blocking the executor.
Rc is !Send because it uses non-atomic reference counting. In multi-threaded or async contexts, use Arc instead. If you're in a single-threaded async runtime, use spawn_local.
Use channels for message passing (decoupled, no contention). Use Mutex/RwLock when you need shared mutable state that multiple threads access concurrently. RwLock is better for read-heavy workloads; Mutex for write-heavy.
The guard is held while the task suspends, blocking other tasks from acquiring the lock. Scope the lock tightly: acquire, use, and drop before calling .await.
Enforce consistent lock ordering (always acquire locks in the same order), use try_lock to detect contention, scope locks tightly, and consider message passing instead of shared mutable state.
Full instructions (SKILL.md)
Source of truth, from actionbook/rust-skills.
name: m07-concurrency description: "CRITICAL: Use for concurrency/async. Triggers: E0277 Send Sync, cannot be sent between threads, thread, spawn, channel, mpsc, Mutex, RwLock, Atomic, async, await, Future, tokio, deadlock, race condition, 并发, 线程, 异步, 死锁" user-invocable: false
Concurrency
Layer 1: Language Mechanics
Core Question
Is this CPU-bound or I/O-bound, and what's the sharing model?
Before choosing concurrency primitives:
- What's the workload type?
- What data needs to be shared?
- What's the thread safety requirement?
Error → Design Question
| Error | Don't Just Say | Ask Instead |
|---|---|---|
| E0277 Send | "Add Send bound" | Should this type cross threads? |
| E0277 Sync | "Wrap in Mutex" | Is shared access really needed? |
| Future not Send | "Use spawn_local" | Is async the right choice? |
| Deadlock | "Reorder locks" | Is the locking design correct? |
Thinking Prompt
Before adding concurrency:
-
What's the workload?
- CPU-bound → threads (std::thread, rayon)
- I/O-bound → async (tokio, async-std)
- Mixed → hybrid approach
-
What's the sharing model?
- No sharing → message passing (channels)
- Immutable sharing → Arc<T>
- Mutable sharing → Arc<Mutex<T>> or Arc<RwLock<T>>
-
What are the Send/Sync requirements?
- Cross-thread ownership → Send
- Cross-thread references → Sync
- Single-thread async → spawn_local
Trace Up ↑ (MANDATORY)
CRITICAL: Don't just fix the error. Trace UP to find domain constraints.
Domain Detection Table
| Context Keywords | Load Domain Skill | Key Constraint |
|---|---|---|
| Web API, HTTP, axum, actix, handler | domain-web | Handlers run on any thread |
| 交易, 支付, trading, payment | domain-fintech | Audit + thread safety |
| gRPC, kubernetes, microservice | domain-cloud-native | Distributed tracing |
| CLI, terminal, clap | domain-cli | Usually single-thread OK |
Example: Web API + Rc Error
"Rc cannot be sent between threads" in Web API context
↑ DETECT: "Web API" → Load domain-web
↑ FIND: domain-web says "Shared state must be thread-safe"
↑ FIND: domain-web says "Rc in state" is Common Mistake
↓ DESIGN: Use Arc<T> with State extractor
↓ IMPL: axum::extract::State<Arc<AppConfig>>
Generic Trace
"Send not satisfied for my type"
↑ Ask: What domain is this? Load domain-* skill
↑ Ask: Does this type need to cross thread boundaries?
↑ Check: m09-domain (is the data model correct?)
| Situation | Trace To | Question |
|---|---|---|
| Send/Sync in Web | domain-web | What's the state management pattern? |
| Send/Sync in CLI | domain-cli | Is multi-thread really needed? |
| Mutex vs channels | m09-domain | Shared state or message passing? |
| Async vs threads | m10-performance | What's the workload profile? |
Trace Down ↓
From design to implementation:
"Need parallelism for CPU work"
↓ Use: std::thread or rayon
"Need concurrency for I/O"
↓ Use: async/await with tokio
"Need to share immutable data across threads"
↓ Use: Arc<T>
"Need to share mutable data across threads"
↓ Use: Arc<Mutex<T>> or Arc<RwLock<T>>
↓ Or: channels for message passing
"Need simple atomic operations"
↓ Use: AtomicBool, AtomicUsize, etc.
Send/Sync Markers
| Marker | Meaning | Example |
|---|---|---|
Send | Can transfer ownership between threads | Most types |
Sync | Can share references between threads | Arc<T> |
!Send | Must stay on one thread | Rc<T> |
!Sync | No shared refs across threads | RefCell<T> |
Quick Reference
| Pattern | Thread-Safe | Blocking | Use When |
|---|---|---|---|
std::thread | Yes | Yes | CPU-bound parallelism |
async/await | Yes | No | I/O-bound concurrency |
Mutex<T> | Yes | Yes | Shared mutable state |
RwLock<T> | Yes | Yes | Read-heavy shared state |
mpsc::channel | Yes | Optional | Message passing |
Arc<Mutex<T>> | Yes | Yes | Shared mutable across threads |
Decision Flowchart
What type of work?
├─ CPU-bound → std::thread or rayon
├─ I/O-bound → async/await
└─ Mixed → hybrid (spawn_blocking)
Need to share data?
├─ No → message passing (channels)
├─ Immutable → Arc<T>
└─ Mutable →
├─ Read-heavy → Arc<RwLock<T>>
└─ Write-heavy → Arc<Mutex<T>>
└─ Simple counter → AtomicUsize
Async context?
├─ Type is Send → tokio::spawn
├─ Type is !Send → spawn_local
└─ Blocking code → spawn_blocking
Common Errors
| Error | Cause | Fix |
|---|---|---|
E0277 Send not satisfied | Non-Send in async | Use Arc or spawn_local |
E0277 Sync not satisfied | Non-Sync shared | Wrap with Mutex |
| Deadlock | Lock ordering | Consistent lock order |
future is not Send | Non-Send across await | Drop before await |
MutexGuard across await | Guard held during suspend | Scope guard properly |
Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|---|---|---|
| Arc<Mutex<T>> everywhere | Contention, complexity | Message passing |
| thread::sleep in async | Blocks executor | tokio::time::sleep |
| Holding locks across await | Blocks other tasks | Scope locks tightly |
| Ignoring deadlock risk | Hard to debug | Lock ordering, try_lock |
Async-Specific Patterns
Avoid MutexGuard Across Await
// Bad: guard held across await
let guard = mutex.lock().await;
do_async().await; // guard still held!
// Good: scope the lock
{
let guard = mutex.lock().await;
// use guard
} // guard dropped
do_async().await;
Non-Send Types in Async
// Rc is !Send, can't cross await in spawned task
// Option 1: use Arc instead
// Option 2: use spawn_local (single-thread runtime)
// Option 3: ensure Rc is dropped before .await
Related Skills
| When | See |
|---|---|
| Smart pointer choice | m02-resource |
| Interior mutability | m03-mutability |
| Performance tuning | m10-performance |
| Domain concurrency needs | domain-* |
Related skills
More from actionbook/rust-skills and the wider catalog.

m09-domain
Model domain concepts as Entities, Value Objects, and Aggregates using Rust ownership and type safety.

m10-performance
Measure, identify bottlenecks, then optimize with data structures, allocation strategies, and parallelism.

m11-ecosystem
Navigate Rust crate selection, dependency management, and ecosystem integration decisions.

m12-lifecycle
Design resource creation, usage, and cleanup patterns using RAII, Drop, and lifecycle strategies.

m13-domain-error
Design domain error handling with categorization, recovery strategies, and resilience patterns.

m14-mental-model
Mental models and analogies for understanding Rust ownership, borrowing, and core concepts.