同一进程中的不同线程,他们读取到同个共享变量的值为什么会不一致

本文探讨了在多处理器环境中,使用volatile关键字确保内存可见性的原理。解释了如何通过强制写入主内存来同步各处理器间的数据,避免因高速缓存导致的不同步问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      处理器可以使用高速缓存加速对内存的访问(或者编译器可以将值存储到寄存器中以便进行更快的访问)。在一些多处理器体系结构上,假如在一个处理器的高速缓存中修改了内存位置,没有必要让其它处理器看到这一修改,直到刷新了写入器的高速缓存并且使读取器的高速缓存无效。
  
  这表示在这样的系统上,对于同一变量,在两个不同处理器上执行的两个线程可能会看到两个不同的值!

      解决这个问题的其中一个方法是用volatile。

      当一个变量被声明成 volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对该变量的读取也都绕过高速缓存,直接取自主内存。这表示所有线程在任何时候看到的 volatile 变量值都相同。

 

<think>我们讨论的是多线程环境下访问全局变量时,为什么不同线程获取到的地址可能不同。这里需要明确几个概念:1.全局变量:在程序的数据段(datasegment)或BSS段(存储未初始化的全局变量)中,整个程序可见。2.线程:每个线程有自己的栈,但是共享进程的堆和全局数据区。然而,问题中提到“获取同一个全局变量,获取到的地址不同”,这通常会发生在真正的全局变量上。因为全局变量在内存中的地址是固定的,所有线程访问的都是同一个地址。但是,有以下几种可能的情况会导致线程访问同一个全局变量时,看到不同的地址:1.线程局部存储(Thread-LocalStorage,TLS):如果全局变量被声明为线程局部存储(例如,在C/C++中使用`__thread`或`thread_local`关键字),那么每个线程都会拥有该变量的一个独立副本,每个副本位于不同的内存地址。2.指针或引用的间接性:如果线程通过指针或引用访问全局变量,并且这个指针或引用本身是线程本地的(比如,每个线程都有一个指向该全局变量的指针,但指针的可能被线程修改指向其他位置),那么不同线程通过自己的指针访问时,可能访问到不同的地址。3.虚假共享(FalseSharing)的误解:虽然虚假共享是关于缓存行的,但并会导致同一变量不同线程中有不同的地址。但是,如果程序员为了缓解虚假共享而将数据复制到线程本地存储,那么每个线程访问的就是自己本地的副本,地址自然不同。4.语言或运行时的特殊机制:在一些语言中,可能会有特殊的机制使得全局变量在多个线程中看起来地址不同。例如,某些解释型语言或带有复杂内存管理的语言(如Python的GIL机制并改变变量地址,但如果有代理或中间层可能会有所不同)但这种情况较少。5.程序错误:例如,在多线程编程中,如果某个线程修改了指向全局变量的指针,那么其他线程如果读取这个指针可能会得到不同。或者,如果全局变量是一个指针,而不同线程修改了这个指针指向不同的内存区域,那么通过这个全局指针访问到的地址就会不同。6.地址空间布局随机化(ASLR):但是ASLR是在程序每次启动时随机化全局变量地址,而在同一进程内,全局变量的地址在运行期间是固定的,会因为线程不同而改变。因此,通常情况下,多个线程访问同一个全局变量,看到的地址应该是相的。如果出现地址不同的情况,很可能是由于使用了线程局部存储,或者程序中有其他错误导致。下面我们用C++代码示例来说明线程局部存储的情况:示例1:普通的全局变量,所有线程看到相的地址。示例2:使用线程局部存储,每个线程有自己的副本,地址不同。注意:在C++11及以上,可以使用`thread_local`关键字。</think>### 多线程中获取同一个全局变量地址不同的原因 在多线程环境中,多个线程访问同一个全局变量时获取到的地址不同,通常由以下原因导致: #### 1. **线程局部存储(Thread-Local Storage, TLS)** - 当全局变量被声明为线程局部(如 C++ 的 `thread_local` 或 Java 的 `ThreadLocal`)时,每个线程会拥有该变量的独立副本。 - 不同线程访问变量时,实际访问的是各自线程中的副本,因此地址不同。 **C++ 示例**: ```cpp #include <iostream> #include <thread> thread_local int globalVar = 0; // 线程局部存储 void threadFunc() { std::cout << "Thread " << std::this_thread::get_id() << ": Address = " << &globalVar << std::endl; } int main() { std::thread t1(threadFunc); std::thread t2(threadFunc); t1.join(); t2.join(); return 0; } ``` **输出**: ``` Thread 140245873055488: Address = 0x7f2b5a7f86f4 Thread 140245864662784: Address = 0x7f2b5aff76f4 ``` - **解释**:`thread_local` 使每个线程拥有独立的 `globalVar` 副本,因此地址不同。 #### 2. **编译器优化(如常量折叠)** - 编译器可能将常量全局变量优化为直接嵌入指令(立即数),导致不同线程看到的"地址"不同(实际是而非地址)。 - 或通过寄存器缓存变量,绕过内存地址访问。 #### 3. **内存模型与缓存一致性** - **写时复制(Copy-on-Write)**:某些系统(如 Linux fork)在修改全局变量时创建副本,导致地址变化。 - **缓存失效**:线程缓存旧的内存地址视图,未及时步最新地址(罕见,通常地址变)。 #### 4. **指针解引用问题** - 若全局变量是指针,不同线程修改指针后,解引用时访问的地址可能不同: ```c int* globalPtr = NULL; // 全局指针 void threadFunc() { int local = 42; globalPtr = &local; // 不同线程修改指针 printf("Pointer value: %p\n", (void*)globalPtr); } ``` - 线程修改 `globalPtr` 后,其他线程看到的指针(地址)会变化。 --- ### 关键结论 - **真正全局变量**:地址固定变,所有线程访问同一内存位置。 - **地址"不同"的常见原因**: 1. 使用了线程局部存储(TLS)。 2. 编译器优化绕过内存访问。 3. 全局变量本身是指针且被修改。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值