بنقرة واحدة
concurrency-audit
新增或修改原子操作、异步定时器、无锁通道、跨线程状态同步、CAS 竞争相关代码时触发。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
新增或修改原子操作、异步定时器、无锁通道、跨线程状态同步、CAS 竞争相关代码时触发。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
写入新模块文档、重构模块、或用户要求分析模块时触发。
Bug 修复或对接问题排查确认有效后,将经验记录到知识库。
修改 PMR 分配器、内存池配置、热路径容器、对象生命周期管理代码后触发。
编写或修改性能基准测试、分析 benchmark 结果、优化热路径性能时触发。
新增或修改 enable_shared_from_this 类、co_spawn 调用、shared_ptr 捕获的 lambda、co_await 后的成员访问时触发。
修改协程(co_await/co_return/co_spawn)、异步 I/O、定时器、多线程相关 C++ 代码时触发。
| name | concurrency-audit |
| description | 新增或修改原子操作、异步定时器、无锁通道、跨线程状态同步、CAS 竞争相关代码时触发。 |
在每线程单执行器的协程架构中,大部分协程天然串行,不需要传统同步原语。但跨线程状态同步、资源限额、异步定时器、协程间信号等场景仍然依赖原子操作和异步通知。这些原语虽然"允许使用",但用错了比不用更危险——错误通常是偶发的、难以复现的、且在特定负载模式下才暴露。
本 skill 与 coroutine-audit 互补:coroutine-audit 管"禁止什么"(禁止阻塞、禁止互斥锁),本 skill 管"允许但要用对"(原子序要用对、定时器要管理好、通道语义要理解)。
std::atomic 成员或修改原子操作的内存序co_spawn(尤其是涉及共享状态的协程)很多开发者习惯所有原子操作都用 memory_order_seq_cst(最强序),觉得"安全"。但在协程架构中:
io_context 上的两个协程永远不会并发执行。对同一执行器上的状态同步,relaxed 足够acq_rel 保证因果性relaxed 是正确选择。过强的序不仅无意义,还会引入不必要的 CPU 屏障开销"检查然后操作"(Time Of Check To Time Of Use)问题存在于所有 CAS 场景中:
在资源限额场景中,TOCTOU 导致的"偶尔多接受一个连接"通常可接受。但如果限额是安全硬约束(如合规要求),则需要更强的机制。
| 检查项 | 说明 |
|---|---|
| 同步模式用 acq_rel | 跨线程的状态同步(如"停止"标志位的发布-订阅、任务队列的入队通知)需要 release(写端)+ acquire(读端)配对。release 保证写端之前的所有写操作对读端可见,acquire 保证读端之后的所有读操作看到最新值 |
| 统计用 relaxed | 计数器、仪表盘等纯统计场景只需要原子性(不撕裂),不需要因果序。relaxed 保证读写的原子性但不保证顺序——对"最终一致即可"的统计完全正确 |
| 标志位用 release/acquire | 单向通知(如"已关闭"、"已初始化")用 release 写入、acquire 读取。比 acq_rel 更精确:写端只保证"之前的写可见",不需要同步读端的状态 |
| 避免 seq_cst 滥用 | seq_cst 是最强序,隐含全局全序。除非需要"所有线程看到相同的操作顺序"(如全局初始化屏障),否则不应使用。过度使用 seq_cst 引入不必要的内存屏障,在 ARM 等弱序架构上性能影响显著 |
| 类型选择正确 | atomic<bool> 和 atomic<uint32_t> 是否选对了类型?用 uint32_t 存 0/1 是反模式,应直接用 atomic<bool>。用 bool 存多状态(0/1/2)是溢出风险 |
| 检查项 | 说明 |
|---|---|
| cancel-then-destroy 顺序 | 定时器销毁前是否先取消?未取消的定时器在销毁后仍可能触发回调,导致访问已销毁的对象。正确模式:先 cancel() 投递取消请求,再销毁定时器对象。在单线程执行器上,cancel 投递的 handler 会在当前协程挂起后执行——如果定时器对象在此之前被销毁,需要确保 handler 中的 self 保活 |
| 共享定时器的双投递语义 | 两个协程共享一个定时器时,第二个 expires_after() 会取消第一个的等待。功能上正确(隐式取消),但依赖执行器的隐式取消保证,重构时容易引入 bug。如果两个协程需要独立的超时,应使用独立的定时器 |
| 取消路径的异常安全 | 定时器取消后 async_wait 返回 operation_aborted 错误码。调用方是否正确处理了这个错误码?如果用 redirect_error 吞掉了错误码,需要确认取消是预期行为而非意外 |
| 定时器回调中的对象存活 | 定时器的 async_wait 回调中引用的对象是否保证在回调触发时仍然存活?如果对象可能先于定时器销毁,需要用 self 捕获保活或在销毁前显式取消定时器 |
| 检查项 | 说明 |
|---|---|
| CAS 失败的降级路径 | CAS 失败后是否有合理的降级?重试(CAS 循环)适用于低竞争场景;异步等待(co_await 信号)适用于高竞争场景。无限重试的 CAS 循环在竞争激烈时退化为忙等待——参见 coroutine-audit 的禁止清单 |
| 限额检查的近似性 | 连接数限额的 CAS 检查在高并发下可能偶尔超出限额(N 个线程同时读到相同计数器值,全部通过检查)。如果严格限额是硬约束,需要更强的同步(如全局计数器 + 独占检查)。如果近似限额可接受,在审计报告中显式标注"近似限额"并说明超出的最大值 |
| ABA 问题的可能性 | 值从 A → B → A 时 CAS 成功但语义错误。典型场景:指针从 P1 → P2 → P1(P1 被回收后重新分配)。对于简单的计数器(只增减不回环),ABA 不是问题。对于指针/索引的 CAS,需要额外机制(如版本号、epoch) |
| exchange 的幂等性 | 用 exchange 设置"已执行一次"标志时,第二次调用是否安全?exchange 返回旧值但不阻止重复调用。如果重复调用有副作用(如重复通知、重复释放),需要在调用前先检查返回值 |
| 检查项 | 说明 |
|---|---|
| 通道容量与背压 | 无界通道不会阻塞发送者,但消费者跟不上时内存无限增长。有界通道在满时阻塞发送者,提供背压但可能导致死锁。选择容量时需要评估消费者的处理速度和峰值缓冲需求 |
| try_send 的丢弃风险 | 非阻塞的 try_send 在通道满时静默丢弃消息。丢弃是否可接受?如果消息是错误通知,丢弃可能导致下游挂起。如果下游同时有其他感知机制(如 transport 关闭),丢弃可接受 |
| 关闭后的消费终止 | 通道关闭后,正在消费的循环是否能正确终止?标准模式是"关闭返回空/错误"。如果关闭后仍尝试 send,需要确认行为是静默失败还是触发断言 |
| 单写者保证 | 通道是否为单写者?如果不是,多个写者的并发发送是否线程安全?标准无锁通道通常支持多写者,但自定义通道可能不支持 |
| 检查项 | 说明 |
|---|---|
| 同执行器无竞争 | 同一 io_context 上的协程天然串行,不构成数据竞争。同执行器上的共享状态不需要原子操作——普通成员变量足够。但需要文档化"此状态仅在 X 执行器上访问"的约定,防止未来维护者误跨线程使用 |
| strand 的必要性 | 如果多个执行器需要访问同一状态,是否使用了 strand(序列化包装器)?strand 保证所有通过它投递的操作串行执行,是"多执行器单状态"的标准解决方案。但 strand 引入额外的调度开销,应仅在必要时使用 |
| 跨线程派发的正确性 | 使用 post(target_executor, handler) 跨线程派发任务时,handler 中访问的对象是否属于目标执行器?跨线程派发后,原线程上的对象状态可能已经变化 |
atomic、定时器、通道、co_spawn 调用// ❌ 所有原子操作都用 seq_cst — 不必要的性能开销
counter_.fetch_add(1, std::memory_order_seq_cst);
flag_.store(true, std::memory_order_seq_cst);
// ✅ 统计用 relaxed,标志用 release
counter_.fetch_add(1, std::memory_order_relaxed);
flag_.store(true, std::memory_order_release);
// ❌ 定时器未取消直接销毁 — 回调可能访问已销毁对象
void on_close()
{
timer_.reset(); // 销毁定时器,但 async_wait 的回调可能已在队列中
}
// ✅ 先取消再销毁
void on_close()
{
timer_.cancel(); // 投递取消请求
// 单线程执行器上,cancel 后的回调在当前协程挂起后执行
timer_.reset();
}
// ❌ 无限 CAS 循环 — 高竞争下退化为忙等待
while (true)
{
auto old = counter_.load();
if (counter_.compare_exchange_weak(old, old - 1))
{
break;
}
// 无降级,持续消耗 CPU
}
// ✅ CAS 快路径 + 异步等待慢路径
while (!acquired && active())
{
auto old = window_.load(std::memory_order_acquire);
if (old >= needed && window_.compare_exchange_weak(old, old - needed))
{
acquired = true;
break;
}
// 降级:异步等待信号,不忙等
co_await signal_->async_wait();
}
// ❌ 错误通知用 try_send — 通道满时丢弃,下游挂起
void on_error(error_code ec)
{
channel_.try_send(ec); // 满时静默丢弃
}
// ✅ 确保错误通知不被丢弃,或使用兜底机制
void on_error(error_code ec)
{
channel_.try_send(ec); // 尝试发送
transport_->close(); // 兜底:关闭 transport,下游感知
}
// ❌ 多个连接并发修改共享的上下文对象
void setup(connection& conn)
{
conn.shared_context()->set_callback(my_callback);
// 另一个线程的连接可能同时修改同一个 shared_context
}
// ✅ 在连接级别设置回调,而非修改共享对象
void setup(connection& conn)
{
conn.set_callback(my_callback); // 修改连接自己的状态
}
coroutine-audit 覆盖了禁止阻塞调用、禁止互斥锁、协程纯度维度co-lifecycle-audit 覆盖了 shared_ptr 循环引用、co_await 后引用失效维度error-chain-audit 覆盖了异步任务的错误处理和异常安全维度crypto-audit 覆盖了常量时间比较、时序侧信道防御维度probe-audit 覆盖了响应时间分布一致性维度