[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fYg9Y6MXLheSQOVJb69K_teXkJIh9ajwag7PhVLGKe_s":3},{"item":4,"related":48},{"id":5,"type":6,"title":7,"slug":8,"summary":9,"body":10,"coverUrl":11,"productScreenshots":12,"productLinks":13,"authorName":14,"authorUrl":15,"authorSubject":16,"category":17,"tags":22,"sourceLabel":39,"sourceName":40,"sourceUrl":41,"status":42,"seoTitle":7,"seoDescription":9,"canonicalUrl":39,"isFeatured":43,"sno":44,"sortOrder":45,"publishedAt":46,"updatedAt":47,"createdAt":47},"b31bdee2-f879-4037-b7a3-1174dc4746ae","article","Compare-and-Swap：并发程序如何用一次原子比较避免互相覆盖？","compare-and-swap-cas-atomic-exchange-explained","CAS 只有在共享值仍等于预期旧值时才执行替换，是无锁数据结构的重要积木。本文解释 compare_exchange 的成功与失败路径、内存序、ABA 问题，并提醒读者无锁不等于自动安全。","多个线程同时修改同一个变量时，最朴素的做法是加锁。但有些场景只需要完成一次很小的状态转换，例如把队列头从旧节点换成新节点，这时可以使用 Compare-and-Swap，简称 CAS，也常见于 `compare_exchange`。\n\n## CAS 做的不是普通赋值\n\nCAS 可以理解成一个原子承诺：“只有当内存里的值仍然等于我刚才看到的旧值，才把它替换成新值；如果别人已经改过，就失败并告诉我当前值。”比较和替换在一个不可被拆开的操作中完成，其他线程不会看到中间状态。\n\n典型的无锁更新会反复尝试：先读取旧值，根据旧值计算新值，再调用 CAS。如果成功，更新完成；如果失败，说明竞争者抢先修改了状态，当前线程重新读取并计算。这个循环就是很多无锁栈、引用计数和并发容器的基本结构。\n\n## CAS 不等于“无锁就安全”\n\nCAS 只保护一次原子状态更新，不能自动保护相关的普通字段。还要正确选择内存序，确保一个线程发布的数据能被另一个线程按预期看到。指针场景还可能遇到 ABA 问题：值看起来从 A 变成 B 又回到 A，CAS 只看到最终仍是 A，却不知道中间发生过变化。\n\n此外，无锁不代表一定更快。高竞争下，多个线程不断失败重试，会浪费 CPU；一个设计得当的互斥锁可能更简单、更稳定。真正重要的是先判断共享状态、竞争程度和正确性要求。\n\n## AI 生成并发代码时要问什么\n\n不要只问“能不能改成无锁”。应该要求 AI 说明原子变量保护的状态、成功与失败路径、内存序、ABA 风险、退避策略和测试方法。并发正确性不能靠读几遍代码凭感觉确认。\n\n## 读者应该记住\n\nCAS 把“读取、比较、写入”合成一次原子动作，是无锁算法的重要积木。但它只解决局部状态转换，不会替你解决内存可见性、生命周期和整体并发协议。\n\n资料：[cppreference：std::atomic](https:\u002F\u002Fen.cppreference.com\u002Fcpp\u002Fatomic\u002Fatomic)","\u002Fuploads\u002F2026-09-20\u002F1e81207f-5b01-43c6-bcdb-0db9562df98c.jpg",[],[],"Foundit","https:\u002F\u002Ffoundit.cn","foundit-ai-editorial",{"id":18,"name":19,"slug":20,"description":21},"6179d3b6-dc34-4483-9ded-3cd9f1b37a47","科普","abbreviation","介绍各领域新兴概念",[23,27,31,35],{"id":24,"name":25,"slug":26},"144abe77-0dc6-4f66-a176-20bddb1c0bfa","编程","coding",{"id":28,"name":29,"slug":30},"a202d639-99a6-488a-a712-4d4c6ffd7e15","开发","dev",{"id":32,"name":33,"slug":34},"7c76bfc2-f80f-4ee0-a95d-27bd8708b434","技术","slug",{"id":36,"name":37,"slug":38},"4c2bbea6-eab7-40a8-8447-1de478ff7749","分析","analyse",null,"cppreference：std::atomic","https:\u002F\u002Fen.cppreference.com\u002Fcpp\u002Fatomic\u002Fatomic","published",false,50,0,"2026-09-20T00:00:00.000Z","2026-09-20T03:58:04.844Z",[49,57,65],{"id":50,"type":6,"title":51,"slug":52,"summary":53,"coverUrl":54,"authorName":14,"sno":55,"publishedAt":46,"createdAt":56},"d0700249-defa-43a1-a66a-4455c6889072","ABI：为什么源码能编译，二进制却不能互相调用？","application-binary-interface-abi-explained","ABI 是二进制世界的调用合同，规定参数传递、对象布局、符号命名和异常处理。本文区分 ABI 与 API，解释动态库、C++ 兼容性和跨语言绑定为什么不能只看函数签名。","\u002Fuploads\u002F2026-09-20\u002F33666a48-4e8f-42a3-9ffd-f4c490498689.jpg",42,"2026-09-20T03:58:01.058Z",{"id":58,"type":6,"title":59,"slug":60,"summary":61,"coverUrl":62,"authorName":14,"sno":63,"publishedAt":46,"createdAt":64},"c9f4e936-9533-4b9c-a1de-f03ef09fed37","WAL：为什么数据库要先写日志，再写真正数据？","write-ahead-logging-wal-database-explained","WAL 要求描述数据变化的日志先于数据页持久化，让数据库可以延迟刷写并在崩溃后通过重放恢复。本文用账本和收据解释 REDO、检查点、复制与持久性设置的关系。","\u002Fuploads\u002F2026-09-20\u002F6260c953-345b-4c9d-b3d6-6202c7e5539c.jpg",43,"2026-09-20T03:58:15.082Z",{"id":66,"type":6,"title":67,"slug":68,"summary":69,"coverUrl":70,"authorName":14,"sno":71,"publishedAt":46,"createdAt":72},"41d50774-50df-4e2f-b1b3-ab6d9f329726","Backpressure：生产者太快时，系统怎样不被数据淹没？","backpressure-reactive-streams-explained","Backpressure 让下游处理能力反过来影响上游生产速度，避免异步流水线靠无限缓存硬撑。本文用水管和阀门解释响应式流、需求信号、数据丢弃与容量设计，也说明它和普通限流的区别。","\u002Fuploads\u002F2026-09-20\u002F35c47792-3d4b-46ae-9375-c68e4c53330e.jpg",45,"2026-09-20T03:58:08.835Z"]