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

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

文章目录

  • 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://www.xdnf.cn/news/20448.html

相关文章:

  • 安装es和kibana
  • Linux之Firewalld防火墙实战篇
  • [光学原理与应用-435]:晶体光学 - 晶体的结构-基元/原胞/晶胞/点阵
  • 多次base64编码过滤垃圾字符
  • 讲一下模版特化和偏特化的区别
  • 如何在Kali Linux官网下载历史版本
  • Redis 持久化机制:AOF 日志深度解析
  • Hystrix与Sentinel-熔断限流
  • 创建阿里云ECS实例操作(免费试用版)
  • 【C++】模板和STL
  • Unity的UGUI更改背景以及添加中文字体
  • 【FastDDS】XML profiles
  • AI助力特征工程:智能化数据科学新范式
  • leetcode 912 排序数组
  • 微前端框架性能对比与选型指南:从理论到实践
  • Redis 的三种高效缓存读写策略!
  • 从技术架构、接入路径、应用场景全梳理的智慧地产开源了
  • C++ 并发编程指南 并发设计模式:Actor vs. CSP (生活场景版)
  • [Upscayl图像增强] Electron主进程命令 | 进程间通信IPC
  • Django 项目6:表单与认证系统
  • PostgreSQL与Greenplum数据库的编程语言连接
  • 深入理解 RequestContextHolder、ThreadLocal 与 RequestContextFilter
  • Spring 基于注解的自动化事务
  • JBoltAI:解锁企业AI数智化升级的Java利器
  • 算法与数据结构实战技巧:从复杂度分析到数学优化
  • 13-Java-面向对象-封装和this关键字
  • Jenkins运维之路(自动获得分支tag自动构建)
  • ComfyUI Easy - Use:简化ComfyUI操作的得力插件
  • echarts实现点击图表添加标记
  • MySQL MHA 高可用集群搭建