首页 > 后端开发 > C++ > 正文

C++如何使用mutex保证内存可见性

P粉602998670
发布: 2025-09-17 10:13:01
原创
777人浏览过
std::mutex通过acquire-release语义建立happens-before关系,确保线程间内存可见性:当一个线程释放锁时,其对共享数据的修改会写回主内存;另一个线程获取同一互斥量时,能读取到最新值,防止重排序与缓存不一致问题。

c++如何使用mutex保证内存可见性

C++中,

std::mutex
登录后复制
主要通过建立“happens-before”关系来保证内存可见性。当一个线程解锁(release)一个互斥量时,它在该互斥量保护区域内对内存的所有修改都会被“同步”到主内存。随后,当另一个线程成功锁定(acquire)同一个互斥量时,它会“看到”之前解锁线程所做的所有内存修改。这确保了共享数据的一致性视图,防止了编译器和CPU的重排序优化破坏多线程程序的正确性。

解决方案

要理解

std::mutex
登录后复制
如何保证内存可见性,我们需要深入C++内存模型(C++ Memory Model)和“happens-before”关系的精髓。这不仅仅是关于锁定和解锁那么简单,它更像是一种契约,编译器和硬件必须遵守的契约。

想象一下,你有一个共享变量

data
登录后复制
和一个布尔标志
ready
登录后复制
。线程A负责计算
data
登录后复制
并设置
ready
登录后复制
true
登录后复制
,线程B则等待
ready
登录后复制
true
登录后复制
后使用
data
登录后复制
。如果没有
mutex
登录后复制
,可能会发生什么?

  • 编译器重排序: 编译器为了优化性能,可能会将线程A中对
    data
    登录后复制
    的写入操作排在对
    ready
    登录后复制
    的写入操作之后。甚至,如果
    ready
    登录后复制
    被设置为
    true
    登录后复制
    ,但
    data
    登录后复制
    还没完全写入,线程B就可能读到旧的或不完整的数据。
  • CPU重排序: 处理器也有自己的乱序执行机制。即使编译器没有重排,CPU也可能在执行指令时,将对
    data
    登录后复制
    的写入延迟,而先处理对
    ready
    登录后复制
    的写入,导致类似的问题。
  • 缓存一致性问题: 每个CPU核心都有自己的缓存。线程A在核心1上修改了
    data
    登录后复制
    ready
    登录后复制
    ,这些修改可能只存在于核心1的缓存中,并没有立即写回主内存。线程B在核心2上运行时,如果直接从核心2的缓存读取,它可能看到的是旧的值。

std::mutex
登录后复制
正是为了解决这些问题而生。它的工作机制可以概括为:

立即学习C++免费学习笔记(深入)”;

  • Acquire 操作 (
    lock()
    登录后复制
    ):
    当一个线程调用
    mutex::lock()
    登录后复制
    时,它执行一个“acquire”操作。这个操作会确保所有在
    lock()
    登录后复制
    之后发生的内存访问,都不能被重排到
    lock()
    登录后复制
    之前。更重要的是,它会确保当前线程的本地缓存与主内存同步,读取到其他线程在
    mutex
    登录后复制
    保护下写入的最新数据。这通常涉及CPU的内存屏障(memory barrier)指令,强制刷新或失效缓存。
  • Release 操作 (
    unlock()
    登录后复制
    ):
    当一个线程调用
    mutex::unlock()
    登录后复制
    时,它执行一个“release”操作。这个操作会确保所有在
    unlock()
    登录后复制
    之前发生的内存访问,都不能被重排到
    unlock()
    登录后复制
    之后。同时,它会强制将当前线程在
    mutex
    登录后复制
    保护下对内存的所有修改从本地缓存写回主内存,使其对其他处理器可见。

所以,当线程A在持有

mutex
登录后复制
时修改了
data
登录后复制
ready
登录后复制
,并在释放
mutex
登录后复制
时,这些修改被保证会写回主内存。当线程B成功获取了同一个
mutex
登录后复制
时,它会强制从主内存中读取最新的
data
登录后复制
ready
登录后复制
值,而不是从其可能过期的本地缓存中读取。这种“先释放后获取”的顺序,在C++内存模型中建立了一个强大的“happens-before”关系链,从而保证了内存可见性。

存了个图
存了个图

视频图片解析/字幕/剪辑,视频高清保存/图片源图提取

存了个图 17
查看详情 存了个图
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <chrono> // For std::this_thread::sleep_for

std::vector<int> shared_data;
std::mutex mtx;
bool data_ready = false; // 共享标志

void producer_thread() {
    // 模拟一些计算耗时
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    // 锁定互斥量,开始修改共享数据
    mtx.lock();
    try {
        std::cout << "Producer: Adding data..." << std::endl;
        for (int i = 0; i < 5; ++i) {
            shared_data.push_back(i * 10);
        }
        data_ready = true; // 设置标志
        std::cout << "Producer: Data added and ready flag set." << std::endl;
    } catch (...) {
        mtx.unlock(); // 确保异常安全解锁
        throw;
    }
    mtx.unlock(); // 释放互斥量
}

void consumer_thread() {
    // 等待数据准备好
    // 注意:这里用一个简单的循环来演示,实际生产中会用条件变量
    // 但为了突出mutex的可见性,这里先简化
    while (true) {
        mtx.lock(); // 尝试获取互斥量
        if (data_ready) {
            std::cout << "Consumer: Data is ready. Reading data..." << std::endl;
            for (int val : shared_data) {
                std::cout << val << " ";
            }
            std::cout << std::endl;
            mtx.unlock(); // 释放互斥量
            break; // 读取完毕,退出循环
        }
        mtx.unlock(); // 释放互斥量,以便生产者可以获取
        std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 避免忙等
    }
}

int main() {
    std::thread producer(producer_thread);
    std::thread consumer(consumer_thread);

    producer.join();
    consumer.join();

    std::cout << "Main: All threads finished." << std::endl;

    return 0;
}
登录后复制

在这个例子中,当

producer_thread
登录后复制
调用
mtx.unlock()
登录后复制
时,
shared_data
登录后复制
data_ready
登录后复制
的所有修改都会被保证写回主内存。当
consumer_thread
登录后复制
成功调用
mtx.lock()
登录后复制
时,它被保证能看到这些最新的修改。如果没有
mutex
登录后复制
consumer_thread
登录后复制
可能会在
data_ready
登录后复制
true
登录后复制
时,仍然读取到空的或不完整的
shared_data
登录后复制
,这就是内存可见性问题。

为什么单独的原子操作不足以保证复杂场景的内存可见性?

有时候,人们会觉得,既然C++11引入了

std::atomic
登录后复制
,并且它也能提供内存同步,那是不是就可以完全替代
mutex
登录后复制
来解决可见性问题了呢?答案是:不完全是。
std::atomic
登录后复制
确实在单个变量的读写操作上提供了强大的内存可见性保证,比如
std::atomic<bool>
登录后复制
std::atomic<int>
登录后复制
,它们可以确保对这些原子变量的修改能被其他线程及时看到,并防止相关的重排序。

然而,当涉及到多个变量之间的数据一致性时,单独的原子操作就显得力不从心了。

std::atomic
登录后复制
保证的是对其自身操作的原子性和可见性,但它无法保证一组非原子操作或者多个原子操作之间的原子性和可见性。

举个例子,如果线程A需要修改

data_a
登录后复制
data_b
登录后复制
两个变量,并且这两个修改必须作为一个不可分割的整体被其他线程看到。如果线程A先修改了
data_a
登录后复制
(原子操作),然后修改了
data_b
登录后复制
(原子操作),在两次修改之间,线程B可能会看到
data_a
登录后复制
的新值和
data_b
登录后复制
的旧值,这导致了数据不一致。
std::atomic
登录后复制
本身无法将这两个独立的原子操作“捆绑”起来。

std::mutex
登录后复制
则不同,它提供的是一个临界区(critical section)的概念。一旦一个线程成功锁定了
mutex
登录后复制
,它就独占了对该
mutex
登录后复制
保护的资源的访问权。在这个临界区内,无论你对多少个变量进行修改,这些修改在
mutex
登录后复制
释放时都会被作为一个整体同步到主内存

以上就是C++如何使用mutex保证内存可见性的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号