名称: m07-并发 描述: “关键:用于并发/异步。触发词:E0277 Send Sync,无法在线程间发送,线程,spawn,通道,mpsc,Mutex,RwLock,Atomic,async,await,Future,tokio,死锁,竞态条件,并发,线程,异步,死锁” 用户可调用: false
并发
第一层:语言机制
核心问题
这是CPU密集型还是I/O密集型,共享模型是什么?
在选择并发原语之前:
- 工作负载类型是什么?
- 需要共享什么数据?
- 线程安全要求是什么?
错误 → 设计问题
| 错误 | 不要只说 | 应该问 |
|---|---|---|
| E0277 Send | “添加Send约束” | 这个类型需要跨线程吗? |
| E0277 Sync | “用Mutex包装” | 真的需要共享访问吗? |
| Future 不是 Send | “使用 spawn_local” | 异步是正确选择吗? |
| 死锁 | “重新排序锁” | 锁的设计正确吗? |
思考提示
在添加并发之前:
-
工作负载是什么?
- CPU密集型 → 线程 (std::thread, rayon)
- I/O密集型 → 异步 (tokio, async-std)
- 混合型 → 混合方法
-
共享模型是什么?
- 不共享 → 消息传递 (通道)
- 不可变共享 → Arc<T>
- 可变共享 → Arc<Mutex<T>> 或 Arc<RwLock<T>>
-
Send/Sync要求是什么?
- 跨线程所有权 → Send
- 跨线程引用 → Sync
- 单线程异步 → spawn_local
向上追溯 ↑ (强制要求)
关键:不要只修复错误。向上追溯以找到领域约束。
领域检测表
| 上下文关键词 | 加载领域技能 | 关键约束 |
|---|---|---|
| Web API, HTTP, axum, actix, handler | domain-web | 处理器在任何线程上运行 |
| 交易, 支付, trading, payment | domain-fintech | 审计 + 线程安全 |
| gRPC, kubernetes, microservice | domain-cloud-native | 分布式追踪 |
| CLI, terminal, clap | domain-cli | 通常单线程即可 |
示例:Web API + Rc 错误
"Rc 无法在线程间发送" 在 Web API 上下文中
↑ 检测:"Web API" → 加载 domain-web
↑ 发现:domain-web 说 "共享状态必须是线程安全的"
↑ 发现:domain-web 说 "状态中的 Rc" 是常见错误
↓ 设计:使用 Arc<T> 和 State 提取器
↓ 实现:axum::extract::State<Arc<AppConfig>>
通用追溯
"我的类型不满足 Send"
↑ 问:这是什么领域?加载 domain-* 技能
↑ 问:这个类型需要跨越线程边界吗?
↑ 检查:m09-domain (数据模型正确吗?)
| 情况 | 追溯到 | 问题 |
|---|---|---|
| Web 中的 Send/Sync | domain-web | 状态管理模式是什么? |
| CLI 中的 Send/Sync | domain-cli | 真的需要多线程吗? |
| Mutex 与通道 | m09-domain | 共享状态还是消息传递? |
| 异步与线程 | m10-performance | 工作负载概况是什么? |
向下追溯 ↓
从设计到实现:
"CPU工作需要并行性"
↓ 使用:std::thread 或 rayon
"I/O需要并发性"
↓ 使用:async/await 与 tokio
"需要在线程间共享不可变数据"
↓ 使用:Arc<T>
"需要在线程间共享可变数据"
↓ 使用:Arc<Mutex<T>> 或 Arc<RwLock<T>>
↓ 或者:用于消息传递的通道
"需要简单的原子操作"
↓ 使用:AtomicBool, AtomicUsize 等
Send/Sync 标记
| 标记 | 含义 | 示例 |
|---|---|---|
Send |
可以在线程间转移所有权 | 大多数类型 |
Sync |
可以在线程间共享引用 | Arc<T> |
!Send |
必须留在一个线程上 | Rc<T> |
!Sync |
不能跨线程共享引用 | RefCell<T> |
快速参考
| 模式 | 线程安全 | 阻塞 | 使用时机 |
|---|---|---|---|
std::thread |
是 | 是 | CPU密集型并行 |
async/await |
是 | 否 | I/O密集型并发 |
Mutex<T> |
是 | 是 | 共享可变状态 |
RwLock<T> |
是 | 是 | 读多写少的共享状态 |
mpsc::channel |
是 | 可选 | 消息传递 |
Arc<Mutex<T>> |
是 | 是 | 跨线程共享可变 |
决策流程图
工作类型是什么?
├─ CPU密集型 → std::thread 或 rayon
├─ I/O密集型 → async/await
└─ 混合型 → 混合方法 (spawn_blocking)
需要共享数据吗?
├─ 否 → 消息传递 (通道)
├─ 不可变 → Arc<T>
└─ 可变 →
├─ 读多写少 → Arc<RwLock<T>>
└─ 写多读少 → Arc<Mutex<T>>
└─ 简单计数器 → AtomicUsize
异步上下文?
├─ 类型是 Send → tokio::spawn
├─ 类型是 !Send → spawn_local
└─ 阻塞代码 → spawn_blocking
常见错误
| 错误 | 原因 | 修复方法 |
|---|---|---|
E0277 Send 不满足 |
异步中的非 Send 类型 | 使用 Arc 或 spawn_local |
E0277 Sync 不满足 |
共享的非 Sync 类型 | 用 Mutex 包装 |
| 死锁 | 锁顺序 | 一致的锁顺序 |
future is not Send |
非 Send 类型跨越 await | 在 await 前释放 |
MutexGuard 跨越 await |
挂起期间持有守卫 | 正确限定守卫作用域 |
反模式
| 反模式 | 为什么不好 | 更好的方法 |
|---|---|---|
| 到处使用 Arc<Mutex<T>> | 竞争、复杂性 | 消息传递 |
| 异步中使用 thread::sleep | 阻塞执行器 | tokio::time::sleep |
| 在 await 期间持有锁 | 阻塞其他任务 | 严格控制锁的作用域 |
| 忽略死锁风险 | 难以调试 | 锁顺序、try_lock |
异步特定模式
避免 MutexGuard 跨越 Await
// 不好:守卫在 await 期间被持有
let guard = mutex.lock().await;
do_async().await; // 守卫仍然被持有!
// 好:限定锁的作用域
{
let guard = mutex.lock().await;
// 使用守卫
} // 守卫被释放
do_async().await;
异步中的非 Send 类型
// Rc 是 !Send,不能在生成的任务中跨越 await
// 选项 1:改用 Arc
// 选项 2:使用 spawn_local (单线程运行时)
// 选项 3:确保 Rc 在 .await 前被释放
相关技能
| 何时 | 参见 |
|---|---|
| 智能指针选择 | m02-resource |
| 内部可变性 | m03-mutability |
| 性能调优 | m10-performance |
| 领域并发需求 | domain-* |