多核时代下线程间的内存可见性

目录

[一 源起](#一 源起)

[二 互斥锁和内存可见性](#二 互斥锁和内存可见性)

[三 原子操作和内存可见性](#三 原子操作和内存可见性)

[四 内存序](#四 内存序)

[五 参考文章](#五 参考文章)


一 源起

现代多核CPU通常具有多级缓存(L1、L2、L3等),每个核心可能有自己的私有L1和L2缓存,而L3缓存可能是共享的。在多核处理器系统中,每个核心可能会在其本地缓存中存储内存位置的副本。这可能导致一个核心上的线程修改了数据,而这个修改没有立即反映到其他核心的缓存中,从而导致缓存不一致为了解决这个问题,现代CPU使用缓存一致性协议(如MESI协议),确保多个CPU核心之间的缓存保持一致。当一个核心修改了它的缓存中的数据时,其他核心的缓存副本将被标记为无效,并在需要时从主内存中重新加载最新数据。这个过程就是保证了当一个线程修改了一个变量的值时其他线程可以立刻访问到修改后的值。

二 互斥锁和内存可见性

mutex使我们最常用的互斥保护方式,使用mutex(互斥锁)可以确保在多线程环境下对共享数据的安全访问。当一个线程获取了mutex锁并修改了共享数据后,其他线程在获取到mutex锁后可以立刻看到更新后的值。这是因为mutex除了提供互斥访问的能力外,还有一个重要的特性就是内存屏障(Memory Barrier)。内存屏障可以防止CPU的指令重排,确保在mutex锁释放之前的所有内存写入操作都对其他线程可见。这就保证了当其他线程在获取到mutex锁后,可以立刻看到更新后的值。但是需要注意的是,这个行为是针对mutex锁的保护下的操作。如果有线程在没有获取mutex锁的情况下访问共享数据,那么就可能看到的是旧的数据,因为这种情况下没有内存屏障的保护。

由此我们可以看到std::mutex本身并不会刷新CPU缓存。然而,当你在多线程环境中使用std::mutex来保护数据时,它可以确保在锁的保护下,对数据的修改对所有线程都是可见的。这是因为在获取和释放锁的过程中,会进行内存屏障操作,这将强制刷新CPU缓存,使得在锁保护下的数据修改对所有线程可见。

三 原子操作和内存可见性

我们重点说原子操作的内存可见性,c++11提供了std::atomic原子操作,std::atomic提供了一种在多线程环境中对数据进行原子操作的方式。原子操作是不可中断的操作,一旦开始就会执行到结束,不会被其他线程打断。这就保证了在多线程环境中,使用std::atomic进行操作的数据在任何时刻都是一致的,不会出现因为线程切换导致的数据不一致的问题,保证一个变量写到一半儿被其他线程读到。并且std::atomic操作也会刷新CPU缓存。这是因为std::atomic提供了一种机制,确保在多线程环境中对特定对象的操作是原子的,即不可中断的。这意味着,一旦一个线程开始一个原子操作,它将在任何其他线程有机会访问该对象之前完成该操作。所以在多核处理器系统中,当一个原子操作完成时,处理器会刷新其缓存,以便其他处理器可以看到最新的数据。然而,这并不意味着所有的std::atomic操作都会导致缓存刷新。具体行为取决于所使用的内存顺序(memory order)。例如,std::memory_order_relaxed就不会导致缓存刷新,而std::memory_order_acquire、std::memory_order_release、std::memory_order_acq_rel和std::memory_order_seq_cst则会。

所以并不是所有的std::atomic操作都有内存可见性的保证,其中std::memory_order_relaxed是最弱的内存序,它只保证了原子操作本身不会被重排,但不保证操作之间的顺序。也就是说,如果线程A在std::memory_order_relaxed模式下修改了一个std::atomic变量,线程B可能看不到这个修改,或者说,线程B看到的可能是一个旧的、未修改的值。

四 内存序

最后说到另一个更加复杂的问题内存序,关于内存序前面已经有几个文章讨论这个问题

揭开内存屏障的面纱

c++原子操作的各种内存序

五 参考文章

mutex如何保证数据的一致性和正确性

c++ std::atomic 数据可见性

C++的std::atomic与体系结构中多核内存模型/缓存一致性协议有什么关系?

Memory Model: 从多处理器到高级语言

相关推荐
Lenyiin41 分钟前
《 C++ 修炼全景指南:十 》自平衡的艺术:深入了解 AVL 树的核心原理与实现
数据结构·c++·stl
程序猿练习生1 小时前
C++速通LeetCode中等第5题-无重复字符的最长字串
开发语言·c++·leetcode
Dola_Pan2 小时前
Linux文件IO(一)-open使用详解
java·linux·dubbo
无名之逆2 小时前
云原生(Cloud Native)
开发语言·c++·算法·云原生·面试·职场和发展·大学期末
Spring-wind2 小时前
【linux】pwd命令
linux
好蛊2 小时前
第 2 课 春晓——cout 语句
c++·算法
ken_coding2 小时前
Windows11 WSL2的ubuntu 22.04中拉取镜像报错
linux·ubuntu·docker
阳光开朗_大男孩儿2 小时前
DBUS属性原理
linux·服务器·前端·数据库·qt
gopher95113 小时前
linux驱动开发-设备树
linux·驱动开发
学习3人组3 小时前
克隆centos网卡uuid相同如何修改
linux·运维·centos