当前位置: 首页 > news >正文

用内存顺序实现 三种内存顺序模型

文章目录

  • C++ 内存顺序:用咖啡店工作模式理解并发编程
    • 前言:为什么需要内存顺序?
    • 三种内存顺序模型
      • 1. 顺序一致性模型 (Sequential Consistency) - 像严格的工作流程
      • 2. 宽松模型 (Relaxed Model) - 像自由工作的咖啡师
      • 3. 获取-释放模型 (Acquire-Release) - 像有协调员的工作流程
    • 解决单例模式的线程安全问题
      • 有问题的单例模式(像没有标准配方的咖啡店)
      • 安全的单例模式(像有标准流程的咖啡店)
    • 综合示例:咖啡店运营模拟
    • 总结:如何选择内存顺序

C++ 内存顺序:用咖啡店工作模式理解并发编程

前言:为什么需要内存顺序?

想象一下,你在一家繁忙的咖啡店工作。有多个咖啡师(线程)同时制作咖啡(处理数据),还有多个顾客(其他线程)等待他们的订单。如果没有明确的工作流程和沟通方式,可能会出现:

  • 咖啡师不知道哪个订单应该先处理
  • 顾客收到错误的咖啡
  • 订单处理顺序混乱导致效率低下

C++ 内存顺序就是为了解决这类问题而设计的一套规则,确保在多线程环境中,各个线程能够有序、高效地协作。

三种内存顺序模型

1. 顺序一致性模型 (Sequential Consistency) - 像严格的工作流程

生活比喻:咖啡店有严格的标准操作流程,每个订单都必须按接收顺序处理,所有咖啡师看到的订单顺序完全一致。

#include <iostream>
#include <atomic>
#include <thread>
#include <cassert>// 咖啡订单状态
std::atomic<bool> order_taken(false);
std::atomic<bool> coffee_made(false);
int customer_satisfaction = 0;void barista() {// 等待接到订单while (!order_taken.load(std::memory_order_seq_cst)) {std::this_thread::yield(); // 等待订单}// 制作咖啡std::this_thread::sleep_for(std::chrono::milliseconds(100));coffee_made.store(true, std::memory_order_seq_cst);std::cout << "咖啡师: 咖啡制作完成!" << std::endl;
}void customer() {// 下订单order_taken.store(true, std::memory_order_seq_cst);std::cout << "顾客: 已下单,等待咖啡..." << std::endl;// 等待咖啡制作完成while (!coffee_made.load(std::memory_order_seq_cst)) {std::this_thread::yield(); // 等待咖啡}// 收到咖啡customer_satisfaction++;std::cout << "顾客: 收到咖啡,非常满意!" << std::endl;
}void test_seq_cst() {std::thread barista_thread(barista);std::thread customer_thread(customer);barista_thread.join();customer_thread.join();assert(customer_satisfaction == 1); // 确保顾客满意std::cout << "测试通过: 顺序一致性模型工作正常" << std::endl;
}

2. 宽松模型 (Relaxed Model) - 像自由工作的咖啡师

生活比喻:咖啡师可以自由决定处理订单的顺序,效率高但可能出现混乱。

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>std::atomic<int> total_coffees(0);void relaxed_barista(int id) {// 模拟制作咖啡时间std::this_thread::sleep_for(std::chrono::milliseconds(10 * (id + 1)));// 使用宽松内存顺序更新计数total_coffees.fetch_add(1, std::memory_order_relaxed);std::cout << "咖啡师 " << id << " 制作了一杯咖啡" << std::endl;
}void test_relaxed() {const int num_baristas = 5;std::vector<std::thread> baristas;// 启动多个咖啡师for (int i = 0; i < num_baristas; ++i) {baristas.emplace_back(relaxed_barista, i);}// 等待所有咖啡师完成for (auto& barista : baristas) {barista.join();}std::cout << "总共制作了 " << total_coffees.load() << " 杯咖啡" << std::endl;
}

3. 获取-释放模型 (Acquire-Release) - 像有协调员的工作流程

生活比喻:有一个协调员确保咖啡师在开始制作前能看到所有必要的订单信息。

#include <iostream>
#include <atomic>
#include <thread>
#include <cassert>
#include <string>std::atomic<std::string*> current_order(nullptr);
std::atomic<bool> order_ready(false);void order_taker() {// 接收订单std::string* order = new std::string("双倍浓缩拿铁");// 发布订单(释放语义)current_order.store(order, std::memory_order_release);std::cout << "订单接收员: 已接收订单 - " << *order << std::endl;
}void barista() {// 获取订单(获取语义)std::string* order;while ((order = current_order.load(std::memory_order_acquire)) == nullptr) {std::this_thread::yield();}std::cout << "咖啡师: 开始制作 - " << *order << std::endl;// 制作咖啡std::this_thread::sleep_for(std::chrono::milliseconds(150));// 标记订单完成(释放语义)order_ready.store(true, std::memory_order_release);std::cout << "咖啡师: 订单完成!" << std::endl;
}void customer() {// 等待订单完成(获取语义)while (!order_ready.load(std::memory_order_acquire)) {std::this_thread::yield();}std::string* order = current_order.load(std::memory_order_relaxed);std::cout << "顾客: 收到我的 " << *order << ",谢谢!" << std::endl;delete order; // 清理内存
}void test_acquire_release() {std::thread t1(order_taker);std::thread t2(barista);std::thread t3(customer);t1.join();t2.join();t3.join();std::cout << "测试通过: 获取-释放模型工作正常" << std::endl;
}

解决单例模式的线程安全问题

让我们用咖啡店的"唯一配方"来比喻单例模式,并解决其线程安全问题。

有问题的单例模式(像没有标准配方的咖啡店)

#include <iostream>
#include <memory>
#include <mutex>
#include <thread>class CoffeeRecipe {
private:CoffeeRecipe() {std::cout << "创建咖啡配方实例" << std::endl;}CoffeeRecipe(const CoffeeRecipe&) = delete;CoffeeRecipe& operator=(const CoffeeRecipe&) = delete;static std::shared_ptr<CoffeeRecipe> instance;static std::mutex instance_mutex;public:~CoffeeRecipe() {std::cout << "销毁咖啡配方实例" << std::endl;}static std::shared_ptr<CoffeeRecipe> getInstance() {// 第一次检查(没有内存屏障,可能看到部分构造的对象)if (!instance) {std::lock_guard<std::mutex> lock(instance_mutex);// 第二次检查if (!instance) {instance = std::shared_ptr<CoffeeRecipe>(new CoffeeRecipe);}}return instance;}void makeCoffee() const {std::cout << "使用秘制配方制作咖啡" << std::endl;}
};std::shared_ptr<CoffeeRecipe> CoffeeRecipe::instance = nullptr;
std::mutex CoffeeRecipe::instance_mutex;void test_unsafe_singleton() {std::thread t1([]() {auto recipe = CoffeeRecipe::getInstance();recipe->makeCoffee();});std::thread t2([]() {auto recipe = CoffeeRecipe::getInstance();recipe->makeCoffee();});t1.join();t2.join();
}

安全的单例模式(像有标准流程的咖啡店)

#include <iostream>
#include <memory>
#include <mutex>
#include <atomic>
#include <thread>class SafeCoffeeRecipe {
private:SafeCoffeeRecipe() {std::cout << "创建安全的咖啡配方实例" << std::endl;}SafeCoffeeRecipe(const SafeCoffeeRecipe&) = delete;SafeCoffeeRecipe& operator=(const SafeCoffeeRecipe&) = delete;static std::shared_ptr<SafeCoffeeRecipe> instance;static std::mutex instance_mutex;static std::atomic<bool> initialized;public:~SafeCoffeeRecipe() {std::cout << "销毁安全的咖啡配方实例" << std::endl;}static std::shared_ptr<SafeCoffeeRecipe> getInstance() {// 使用获取语义检查是否已初始化if (!initialized.load(std::memory_order_acquire)) {std::lock_guard<std::mutex> lock(instance_mutex);// 再次检查(使用宽松语义,因为已经在锁内)if (!initialized.load(std::memory_order_relaxed)) {instance = std::shared_ptr<SafeCoffeeRecipe>(new SafeCoffeeRecipe);// 使用释放语义标记已初始化initialized.store(true, std::memory_order_release);}}return instance;}void makeCoffee() const {std::cout << "使用安全秘制配方制作咖啡" << std::endl;}
};std::shared_ptr<SafeCoffeeRecipe> SafeCoffeeRecipe::instance = nullptr;
std::mutex SafeCoffeeRecipe::instance_mutex;
std::atomic<bool> SafeCoffeeRecipe::initialized = false;void test_safe_singleton() {std::thread t1([]() {auto recipe = SafeCoffeeRecipe::getInstance();recipe->makeCoffee();});std::thread t2([]() {auto recipe = SafeCoffeeRecipe::getInstance();recipe->makeCoffee();});t1.join();t2.join();
}

综合示例:咖啡店运营模拟

让我们创建一个完整的咖啡店模拟,展示不同内存顺序在实际场景中的应用。

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <chrono>
#include <random>class CoffeeShop {
private:// 使用获取-释放模型协调订单处理std::atomic<int> pending_orders{0};std::atomic<int> completed_orders{0};// 使用宽松模型统计咖啡师工作量std::atomic<int> barista1_work{0};std::atomic<int> barista2_work{0};// 顺序一致性模型用于关键状态std::atomic<bool> shop_open{false};public:void open() {shop_open.store(true, std::memory_order_seq_cst);std::cout << "咖啡店开业了!" << std::endl;}void close() {shop_open.store(false, std::memory_order_seq_cst);std::cout << "咖啡店打烊了!" << std::endl;}void take_order(int customer_id) {// 模拟接收订单时间std::this_thread::sleep_for(std::chrono::milliseconds(10));// 发布新订单(释放语义)pending_orders.fetch_add(1, std::memory_order_release);std::cout << "顾客 " << customer_id << " 下了订单" << std::endl;}void prepare_order(int barista_id) {while (shop_open.load(std::memory_order_seq_cst)) {// 获取待处理订单(获取语义)int orders = pending_orders.load(std::memory_order_acquire);if (orders > 0) {// 尝试接单if (pending_orders.compare_exchange_strong(orders, orders - 1, std::memory_order_acq_rel)) {// 制作咖啡std::cout << "咖啡师 " << barista_id << " 开始制作咖啡" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));// 完成订单(释放语义)completed_orders.fetch_add(1, std::memory_order_release);// 更新工作量统计(宽松语义)if (barista_id == 1) {barista1_work.fetch_add(1, std::memory_order_relaxed);} else {barista2_work.fetch_add(1, std::memory_order_relaxed);}std::cout << "咖啡师 " << barista_id << " 完成一杯咖啡" << std::endl;}} else {std::this_thread::yield();}}}void run() {open();// 启动咖啡师std::thread barista1(&CoffeeShop::prepare_order, this, 1);std::thread barista2(&CoffeeShop::prepare_order, this, 2);// 模拟顾客下单std::vector<std::thread> customers;for (int i = 0; i < 10; ++i) {customers.emplace_back(&CoffeeShop::take_order, this, i);}// 等待所有顾客下单for (auto& customer : customers) {customer.join();}// 等待所有订单完成while (completed_orders.load(std::memory_order_acquire) < 10) {std::this_thread::sleep_for(std::chrono::milliseconds(100));}close();barista1.join();barista2.join();// 输出统计信息std::cout << "今日营业结束:" << std::endl;std::cout << "咖啡师1制作了 " << barista1_work.load() << " 杯咖啡" << std::endl;std::cout << "咖啡师2制作了 " << barista2_work.load() << " 杯咖啡" << std::endl;std::cout << "总共完成了 " << completed_orders.load() << " 个订单" << std::endl;}
};int main() {CoffeeShop shop;shop.run();return 0;
}

总结:如何选择内存顺序

通过咖啡店的比喻,我们可以更好地理解不同内存顺序的适用场景:

  1. 顺序一致性 (seq_cst):像严格的标准操作流程,保证所有操作顺序一致,但性能开销最大。适用于需要强一致性的关键操作。

  2. 获取-释放 (acquire-release):像有协调员的工作流程,保证相关操作的顺序,性能适中。适用于需要同步但不要求全局顺序一致的场景。

  3. 宽松模型 (relaxed):像自由工作的咖啡师,只保证原子性,不保证顺序,性能最高。适用于统计、计数等不需要同步的场景。

在实际开发中,应该根据具体需求选择最合适的内存顺序,在保证正确性的前提下追求性能。就像经营咖啡店一样,需要在标准流程和灵活性之间找到平衡点。


文章转载自:

http://04lQOi7D.wbLpn.cn
http://lY50OMI7.wbLpn.cn
http://5dinpecQ.wbLpn.cn
http://xQJp6yko.wbLpn.cn
http://EOGOgU5G.wbLpn.cn
http://LHn0lPSc.wbLpn.cn
http://ie2oENHE.wbLpn.cn
http://yF4pDS0R.wbLpn.cn
http://jy0zpiml.wbLpn.cn
http://Jlrb3uLP.wbLpn.cn
http://5mtnGu7P.wbLpn.cn
http://9JJBmuSX.wbLpn.cn
http://U8cwJaDK.wbLpn.cn
http://cas72dmy.wbLpn.cn
http://ZBmuf0U8.wbLpn.cn
http://YsnMFFbo.wbLpn.cn
http://FWZEGiml.wbLpn.cn
http://xBBmCSiT.wbLpn.cn
http://pvxpqRD0.wbLpn.cn
http://Qz2z4eox.wbLpn.cn
http://mnaP6Gyf.wbLpn.cn
http://UHMrvRwK.wbLpn.cn
http://7osI5XAJ.wbLpn.cn
http://hRyW7WqV.wbLpn.cn
http://yiDieZS6.wbLpn.cn
http://ZRCfG99k.wbLpn.cn
http://oy5akCFH.wbLpn.cn
http://Fqh7KM5y.wbLpn.cn
http://I3tlGv2W.wbLpn.cn
http://ccjih7lb.wbLpn.cn
http://www.dtcms.com/a/370458.html

相关文章:

  • rh134第五章复习总结
  • Java包装类型
  • Linux awk 命令使用说明
  • 一个正常的 CSDN 博客账号,需要做哪些基础准备?
  • 文件I/O与I/O多路复用
  • protobuf的序列反序列化
  • Linux/UNIX系统编程手册笔记:共享库、进程间通信、管道和FIFO、内存映射以及虚拟内存操作
  • 吴恩达机器学习(九)
  • 基于多级特征编码器用于声学信号故障检测模型
  • 【LeetCode热题100道笔记】二叉树中的最大路径和
  • The Open Group 宣布成立Industrial Advanced Nuclear™ 联盟)
  • 问题:指令译码前控制信号还没有产生,那么如何控制译码前指令的动作呢?
  • 软件测试理论
  • Wisdom SSH 是一款创新性工具,通过集成 AI 助手,为服务器性能优化带来极大便利。
  • ChatGPT下的相关聊天提示词
  • C# WinForm分页控件实现与使用详解
  • 在Ubuntu平台搭建RTMP直播服务器使用SRS简要指南
  • 设计艺术~缓存结构设计
  • 腾讯混元游戏视觉生成平台正式发布2.0版本
  • MySQL整理【01】
  • MQTT 与 Java 框架集成:Spring Boot 实战(三)
  • docker 推送仓库(含搭建、代理等)
  • 了解Python
  • LeetCode //C - 848. Shifting Letters
  • 数学判官为中医续命
  • 华为OmniPlacement技术深度解析:突破超大规模MoE模型推理瓶颈的创新设计
  • Web Worker 从原理到实战 —— 把耗时工作搬到后台线程,避免页面卡顿
  • [网络入侵AI检测] docs | 任务二分类与多分类
  • Browser Use:打造你的浏览器自动化助手
  • 全维度质量保障:捷多邦厚铜板控制方法详解