C++核心指导原则: 并发和并行

C++ Core Guidelines 整理目录

  1. 哲学部分
  2. 接口(Interface)部分
  3. 函数部分
  4. 类和类层次结构部分
  5. 枚举部分
  6. 资源管理部分
  7. 表达式和语句部分
  8. 性能部分
  9. 并发和并行
  10. 错误处理
  11. 常量和不可变性
  12. 泛型编程
  13. 源文件
  14. 命名和布局建议
  15. 标准库
  16. 其他规则

一般规则

CP.1: Assume that your code will run as part of a multi-threaded program
  • 解释: 假设你的代码将会作为多线程程序的一部分运行.
  • 原因: 即使当前代码看起来是单线程的, 未来的扩展或集成到更大的系统中可能需要并发执行. 提前考虑并发问题可以在设计上留有余地.
CP.2: Avoid data races
  • 解释: 避免数据竞争, 确保对共享数据的访问是同步的.
  • 原因: 数据竞争会导致未定义行为, 包括程序崩溃, 不正确的结果等. 正确地管理共享资源可以提高程序的稳定性和可靠性.
CP.3: Minimize explicit sharing of writable data
  • 解释: 尽量减少显式共享可写数据.
  • 原因: 减少直接共享数据可以降低数据竞争的风险, 并简化并发控制逻辑. 这有助于提升代码的可维护性和性能.
CP.4: Think in terms of tasks, rather than threads
  • 解释: 应该以任务而非线程来思考.
  • 原因: 以任务为导向的设计使得开发者更容易关注于要完成的工作而不是底层的执行机制. 这种方法提高了资源利用效率, 简化了并行编程.
CP.8: Don’t try to use volatile for synchronization
  • 解释: 不要尝试使用volatile来进行同步操作.
  • 原因: volatile关键字不能保证原子性或互斥, 它仅用于防止编译器优化掉对变量的读写操作. 应使用适当的同步原语(如互斥锁)来确保线程安全.
CP.9: Whenever feasible use tools to validate your concurrent code
  • 解释: 尽可能使用工具验证你的并发代码.
  • 原因: 并发错误往往难以复现和调试. 利用静态分析工具, 动态分析工具等可以帮助检测死锁, 竞态条件等问题, 从而提高代码质量并减少潜在的错误. Thread Sanitizer(TSAN)可以帮助检测并发错误.

并发规则

CP.20: Use RAII, never plain lock()/unlock()
  • 解释: 使用资源获取即初始化(RAII)机制来管理锁, 而不是直接调用 lock()unlock().
  • 原因: RAII 机制可以确保锁的释放, 从而避免死锁和竞争条件. 锁相关的主题, 可以参考我的这篇博客: [现代 C++锁介绍]({{<ref “/posts/2024-12-24-modern-cpp-lock-intro.md” >}})
CP.21: Use std::lock() or std::scoped_lock to acquire multiple mutexes
  • 解释: 使用std::lock()std::scoped_lock来获取多个互斥锁.
  • 原因: 这些机制可以避免死锁问题, 因为它们保证了锁的顺序一致性和自动释放. 锁相关的主题, 可以参考我的这篇博客: [现代 C++锁介绍]({{<ref “/posts/2024-12-24-modern-cpp-lock-intro.md” >}})
CP.22: Never call unknown code while holding a lock (e.g., a callback)
  • 解释: 持有锁时不要调用未知代码(例如回调函数).
  • 原因: 未知代码可能执行长时间操作或尝试获取其他锁, 导致潜在的死锁或性能问题.
CP.23: Think of a joining thread as a scoped container
  • 解释: 将加入线程视为作用域容器.
  • 原因: 这种思维方式确保线程在其生命周期内正确管理资源, 并在作用域结束时自动清理.
CP.24: Think of a thread as a global container
  • 解释: 将线程视为全局容器.
  • 原因: 提醒开发者注意线程之间共享数据的安全性, 确保对共享资源进行适当的同步.
CP.25: Prefer gsl::joining_thread over std::thread
  • 解释: 优先使用gsl::joining_thread而不是std::thread.
  • 原因: gsl::joining_thread提供了更安全的接口, 默认情况下会在析构时等待线程完成, 减少资源泄漏的风险. C++20 有了std::jthread, 可以使用std::jthread来替换gsl::joining_thread. 关于 jthread, 可以参考我的这篇博客: [C++20 中的 jthread: 从 RAII 到线程管理的进化]({{<ref “/posts/2024-08-01-cpp20-jthread.md” >}})
CP.26: Don’t detach() a thread
  • 解释: 不要分离线程.
  • 原因: 分离线程可能导致资源无法正确回收, 增加内存泄漏和其他资源管理问题的风险.
CP.31: Pass small amounts of data between threads by value, rather than by reference or pointer
  • 解释: 在线程间通过值传递少量数据, 而不是引用或指针.
  • 原因: 值传递避免了共享可变状态带来的复杂性和潜在的数据竞争问题.
CP.32: To share ownership between unrelated threads use shared_ptr
  • 解释: 在不相关的线程之间共享所有权时使用shared_ptr.
  • 原因: shared_ptr提供了线程安全的所有权管理和引用计数, 减少了手动管理资源的复杂性.
CP.40: Minimize context switching
  • 解释: 尽量减少上下文切换.
  • 原因: 上下文切换会带来额外的开销, 影响系统性能. 合理设计任务调度可以减少不必要的切换.
CP.41: Minimize thread creation and destruction
  • 解释: 尽量减少线程的创建和销毁.
  • 原因: 线程的创建和销毁是昂贵的操作, 建议使用线程池等技术来重用线程, 提高效率.
CP.42: Don’t wait without a condition
  • 解释: 不要在没有条件的情况下等待.
  • 原因: 无条件等待会导致线程长时间阻塞, 浪费资源. 应确保等待是有条件且合理的.
CP.43: Minimize time spent in a critical section
  • 解释: 尽量减少在临界区花费的时间.
  • 原因: 长时间占用锁会增加其他线程等待的时间, 降低并发性能.
CP.44: Remember to name your lock_guards and unique_locks
  • 解释: 记得为你的lock_guardunique_lock命名.
  • 原因: 明确命名有助于代码的可读性和维护性, 便于理解锁定的意图和范围.
CP.50: Define a mutex together with the data it guards. Use synchronized_value<T> where possible
  • 解释: 定义互斥锁时一并定义它保护的数据. 尽可能使用synchronized_value<T>.
  • 原因: 这种封装方式提高了代码的安全性和可读性, 确保数据访问的一致性和同步性.

协程规则

CP.51: Do not use capturing lambdas that are coroutines
  • 解释: 不要使用捕获协程的 lambda 表达式.
  • 原因: 捕获协程的 lambda 可能会导致复杂的生命周期问题, 并且难以管理资源和状态.
CP.52: Do not hold locks or other synchronization primitives across suspension points
  • 解释: 不要在挂起点持有锁或其他同步原语.
  • 原因: 在挂起点持有锁会导致潜在的死锁问题, 并且增加其他线程等待的时间, 降低并发性能.
CP.53: Parameters to coroutines should not be passed by reference
  • 解释: 协程的参数不应通过引用传递.
  • 原因: 引用传递可能导致悬空指针或未定义行为, 尤其是在协程挂起和恢复的过程中. 值传递更加安全和明确.

消息传递规则

CP.60: Use a future to return a value from a concurrent task
  • 解释: 使用future从并发任务中返回值.
  • 原因: future提供了一种简单且线程安全的方式, 允许异步操作的结果在稍后被检索. 关于 future, 可以参考我的这篇博客: [C++异步任务: std::async, std::future 和 std::promise]({{<ref “/posts/2024-12-31-cpp-future-and-promise.md” >}}).
CP.61: Use async() to spawn concurrent tasks
  • 解释: 使用async()来启动并发任务.
  • 原因: async()简化了并发任务的创建和管理, 提供了自动化的线程池管理和结果获取机制.

无锁编程规则

CP.100: Don’t use lock-free programming unless you absolutely have to
  • 解释: 不要使用无锁编程, 除非绝对必要.
  • 原因: 无锁编程复杂且容易出错, 通常需要深入理解底层硬件和内存模型. 只有在性能要求极高且无法通过其他方式满足时才考虑使用.
CP.101: Distrust your hardware/compiler combination
  • 解释: 对你的硬件/编译器组合保持怀疑态度.
  • 原因: 不同硬件和编译器对无锁编程的支持和实现可能不同, 这可能导致不可预见的行为和错误. 仔细验证代码在目标平台上的正确性.
CP.102: Carefully study the literature
  • 解释: 仔细研究相关文献.
  • 原因: 无锁编程涉及复杂的概念和技术, 深入学习现有的研究成果和最佳实践有助于避免常见陷阱和错误.
CP.110: Do not write your own double-checked locking for initialization
  • 解释: 不要自己编写双重检查锁定用于初始化.
  • 原因: 双重检查锁定模式虽然看似简单, 但在多线程环境中容易出错, 特别是在处理内存顺序和可见性问题时. 应使用经过验证的标准库或模式.
CP.111: Use a conventional pattern if you really need double-checked locking
  • 解释: 如果确实需要双重检查锁定, 请使用标准模式.
  • 原因: 标准模式(如 C++11 中的std::call_once)经过充分测试和验证, 能够正确处理并发初始化问题, 避免手动实现带来的复杂性和风险.

其他规则

CP.200: Use volatile only to talk to non-C++ memory
  • 解释: 仅在与非 C++内存进行通信时使用volatile.
  • 原因: volatile关键字用于指示编译器不要优化对变量的访问, 但通常仅在与非 C++内存进行通信时使用.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

arong-xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值