实现内存可见性的方式

今天看一下实现内存可见性的方式
首先看一下一些概念:
可见性:一个线程对共享变量值的修改,能够及时的被其他线程看到。
共享变量:如果一个变量在每个线程的工作内存中都有副本,那么这个变量就是这些线程的共享变量。

下面讲一下关于Java内存的一些东西

  • 所有的变量都存储在主内存
  • 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一个拷贝)

Synchronized可以实现

  • 原子性
  • 可见性

Java内存模型的两条规定

  • 线程对共享变量的所有操作必须在自己的工作内存中进行,不能直接从住内存中读写
  • 不同线程直接不能直接访问其他线程工作内存中的变量,线程间变量的传递需要通过主内存。

指令重排序
代码的书写顺序和执行顺序不一致,重排序是编译器或者处理器为了提高程序性能所做的优化。

as-if-serial语义
无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致。重排序不会给单线程造成内存可见性问题

if(ready){
result = number*3;
}

上面的代码也可能重排序,只有数据依赖关系才会禁止重排序,上面的代码等同于

int mid = 3;
if(ready){
result = mid;
}

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

<think>好的,我现在需要回答用户关于C98如何保证内存可见性的问题。用户之前询问过C11中使用内存屏障和原子操作的示例,现在转向更早的C98标准。我需要明确C98本身是否支持这些特性,以及当时开发者是如何处理内存可见性的。 首先,我记得C98标准本身并没有像C11那样引入原子操作或内存模型。C11之前,C语言的标准库并不包含stdatomic.h,也没有明确的内存顺序参数。因此,在C98中,处理多线程和内存可见性需要依赖其他方法。 接下来,我应该考虑在C98时代,常见的做法是什么。通常,开发者会使用编译器提供的扩展或者特定的库函数来处理多线程同步。例如,GCC的内置原子操作函数,或者使用POSIX线程(pthreads)库中的互斥锁和屏障。另外,volatile关键字可能被用来防止编译器优化,但它并不保证内存可见性,只能避免编译器级别的优化。 然后,需要解释volatile在C98中的作用,以及它的局限性。volatile告诉编译器变量可能会被意外修改,因此每次访问都从内存中读取,但它不处理CPU层面的指令重排或缓存一致性问题。因此,仅用volatile无法保证多线程环境下的可见性,必须结合其他同步机制。 接下来,应该提到内存屏障的问题。在C98中,没有标准的API来插入内存屏障,但不同的编译器可能提供内联汇编或内置函数来实现。例如,GCC的__sync_synchronize()函数可以插入全内存屏障。这需要编译器特定的扩展,因此代码的可移植性会受到影响。 此外,互斥锁(mutex)是确保内存可见性的常用方法。使用pthread_mutex_lock和pthread_mutex_unlock可以创建临界区,保证锁内操作的可见性和原子性。这是因为互斥锁的实现通常包含了必要的内存屏障,确保在锁释放前的所有写操作对其他线程可见。 还需要提到条件变量和其他同步原语,如信号量,它们也可以用来协调线程间的操作,间接保证内存可见性。例如,pthread_cond_wait和pthread_cond_signal配合互斥锁使用,可以确保在等待条件变量时,相关的内存修改对其他线程可见。 另外,应该指出C98环境下,编写跨平台的多线程代码存在挑战,因为不同编译器和平台可能有不同的实现方式。开发者需要依赖特定平台的文档和扩展,这增加了复杂性和维护成本。 最后,总结C98中保证内存可见性的方法主要包括使用互斥锁、编译器特定的原子操作和内存屏障,以及谨慎使用volatile关键字。同时,需要强调这些方法不如C11的原子操作和内存模型那样标准化和可靠,因此在现代代码中推荐使用C11或更高版本的标准。</think>在C98标准中,由于没有内置的原子操作和明确的内存模型,开发者需要通过以下方式保证多线程环境中的内存可见性: --- ### **核心方法** #### 1. **互斥锁(Mutex)** ```c #include <pthread.h> int shared_data = 0; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void* thread_func(void* arg) { pthread_mutex_lock(&lock); // 进入临界区 shared_data++; // 受保护操作 pthread_mutex_unlock(&lock); // 退出临界区 return NULL; } ``` - **作用**:通过锁机制隐式实现内存屏障,保证临界区内操作的可见性 - **原理**:锁的获取和释放操作中会插入硬件级内存屏障(如x86的`lock`前缀指令) --- #### 2. **volatile关键字(有限使用)** ```c volatile int flag = 0; // 防止编译器优化 void writer() { // 写操作可能对其他线程不可见 flag = 1; } void reader() { while (flag == 0); // 可能无法及时看到更新 } ``` - **作用**: - 阻止编译器优化(如缓存寄存器值) - **不保证**CPU级别的可见性(如缓存一致性) - **适用场景**:仅适用于单核系统或配合内存屏障使用 --- #### 3. **编译器扩展的原子操作** (以GCC为例) ```c int val = 0; // 原子加法 void atomic_add() { __sync_fetch_and_add(&val, 1); } // 内存屏障 void barrier() { __sync_synchronize(); // 全内存屏障 } ``` - **支持的扩展**: - `__sync_*`系列函数(GCC 4.1+) - `_ReadWriteBarrier()`(MSVC) --- #### 4. **显式内存屏障(平台相关)** ```c // x86架构(通过内联汇编) #define mb() __asm__ __volatile__("mfence" ::: "memory") // ARM架构 #define dmb() __asm__ __volatile__("dmb ish" ::: "memory") ``` - **典型使用**: ```c shared_data = 42; mb(); // 写屏障 flag = 1; // 同步标记 ``` --- ### **内存可见性保证原理** 1. **写可见性保证**: $$ \text{写操作} \xrightarrow{\text{内存屏障}} \text{其他线程可见} $$ 2. **读可见性保证**: $$ \text{内存屏障} \xrightarrow{\text{刷新本地缓存}} \text{读取最新值} $$ --- ### **典型代码模式** #### 生产者-消费者模型 ```c volatile int data_ready = 0; int payload = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 生产者 void* producer(void* arg) { pthread_mutex_lock(&mutex); payload = 42; // 写数据 __sync_synchronize(); // 内存屏障 data_ready = 1; // 发布标志 pthread_mutex_unlock(&mutex); return NULL; } // 消费者 void* consumer(void* arg) { while(1) { pthread_mutex_lock(&mutex); __sync_synchronize(); // 内存屏障 if(data_ready) { printf("%d\n", payload); break; } pthread_mutex_unlock(&mutex); } return NULL; } ``` --- ### **各平台差异对比** | 机制 | x86 | ARM | PowerPC | |------------------|--------------------|-------------------|------------------| | 默认内存序 | 强一致性(TSO) | 弱一致性 | 弱一致性 | | 需要显式屏障场景 | 较少 | 频繁 | 频繁 | | 锁实现 | `LOCK`前缀指令 | `LDREX/STREX` | `lwarx/stwcx` | --- ### **局限性** 1. **无标准保证**:依赖具体编译器和硬件实现 2. **性能损耗**:频繁的锁操作和全屏障影响性能 3. **错误风险**: - 忘记使用屏障导致可见性问题 - 误用volatile造成虚假安全感 --- ### **现代替代方案** 虽然C98缺少原生支持,但在实际工程中: 1. 使用POSIX线程库的完整同步原语 2. 依赖编译器提供的原子操作扩展 3. 对于高性能场景,直接使用汇编实现关键部分 建议新项目优先使用C11及以上标准,其通过`<stdatomic.h>`和内存顺序参数提供了标准化解决方案。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值