减少锁的竞争
缩小临界区:只将必须互斥的代码用锁保护起来,尽快释放锁。避免在临界区内进行耗时操作(如I/O、复杂计算)。
使用更细粒度的锁:不要用一个“大锁”保护所有数据。使用多个锁保护不同的数据,允许线程并行访问不同的资源。(例如,一个锁保护用户列表,另一个锁保护日志文件)。
使用读写锁:如
std::shared_mutex。适用于读多写少的场景,允许多个读者同时访问,写者独占访问。
优化数据布局和访问
避免伪共享(False Sharing):
问题:两个线程频繁写入两个在物理上位于同一缓存行(Cache Line)(通常是64字节)但逻辑上无关的变量。这会导致缓存行在不同CPU核心间无效地来回跳动,严重损害性能。
解决:将对性能关键的、被不同线程频繁写入的变量隔离到不同的缓存行中。可以通过字节填充(
alignas(64))或让每个变量独占一个缓存行来实现。
保证真共享(True Sharing) 的有效性:让需要被同一线程组频繁访问的数据尽可能地靠近(在内存中相邻),以提高缓存利用率。
任务划分与负载均衡
任务并行 vs. 数据并行:选择最适合问题的模型。数据并行通常更容易实现负载均衡。
负载均衡(Load Balancing):确保所有线程的工作量大致相等,避免某些线程早早完工而其他线程还在忙碌。
系统与硬件考量
CPU 亲和性(CPU Affinity):将线程绑定到特定的CPU核心上,可以减少缓存失效和上下文切换的开销,尤其在对延迟极其敏感的场景(如高频交易)中很重要。
理解内存层次结构:意识到访问内存、L1/L2/L3缓存的速度差异巨大。优化目标之一是提高缓存命中率(Cache Hit Rate)。
线程数并非越多越好:线程数超过物理核心数时会引入额外的上下文切换开销。通常推荐线程池的大小为
std::thread::hardware_concurrency()或稍多一些(如果线程包含I/O等待)。
评论区