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

并发编程指南 同步操作与强制排序

文章目录

  • 5.3 同步操作与强制排序
    • 代码5.2 多线程数据读写示例
    • 5.3.1 同步发生
    • 5.3.2 先行发生
    • 5.3.3 原子操作的内存序
      • 顺序一致性序
      • 自由序
      • 获取-释放序
    • 5.3.4 释放序列与同步
    • 5.3.5 栅栏
    • 5.3.6 原子操作对非原子操作排序
    • 5.3.7 非原子操作排序

5.3 同步操作与强制排序

在多线程编程中,当多个线程同时访问共享数据时,需要谨慎处理同步问题。让我们通过一个简单例子来理解这个概念:假设一个线程向数据结构写入数据,另一个线程从中读取数据。为了避免数据竞争,写入线程会设置一个标志位表示数据已准备就绪,读取线程则需等待该标志位被设置后才能读取数据。

代码5.2 多线程数据读写示例

#include <vector>
#include <atomic>
#include <iostream>
#include <thread>std::vector<int> data;
std::atomic<bool> data_ready(false);void reader_thread()
{while(!data_ready.load())  // 1. 等待数据准备就绪{std::this_thread::sleep(std::chrono::milliseconds(1));}std::cout << "The answer=" << data[0] << "\n";  // 2. 读取数据
}void writer_thread()
{data.push_back(42);  // 3. 写入数据data_ready = true;   // 4. 设置数据就绪标志
}int main()
{std::thread writer(writer_thread);std::thread reader(reader_thread);writer.join();reader.join();return 0;
}

在这个例子中,虽然等待循环①本身是原子的,但非原子读取操作②和写入操作③如果无序执行,就会产生未定义行为。我们通过原子变量data_ready的操作来建立执行顺序:数据写入③必须先于标志设置④,标志检查①必须先于数据读取②。当data_ready为true时,写操作与读操作同步,建立了"先行"关系。

5.3.1 同步发生

"同步发生"关系只在原子类型操作间存在。当线程A执行原子写操作,线程B执行原子读操作且读取的是A写入的值(或之后写入的值),那么A的写操作与B的读操作就是同步发生关系。

5.3.2 先行发生

"先行发生"关系是程序操作顺序的基本构建块。在单线程中,如果操作A在操作B之前执行,那么A就先行于B。在多线程环境中,如果操作A与另一线程上的操作B同步发生,那么A线程间先行于B。

5.3.3 原子操作的内存序

C++提供了六种内存序选项:

  1. memory_order_relaxed - 自由序
  2. memory_order_consume - 消费序(C++17中不推荐使用)
  3. memory_order_acquire - 获取序
  4. memory_order_release - 释放序
  5. memory_order_acq_rel - 获取-释放序
  6. memory_order_seq_cst - 顺序一致性序(默认)

顺序一致性序

顺序一致性是最严格的内存序,保证所有线程看到的操作顺序一致。下面是顺序一致性的示例:

#include <atomic>
#include <thread>
#include <assert.h>std::atomic<bool> x, y;
std::atomic<int> z;void write_x() { x.store(true, std::memory_order_seq_cst); }
void write_y() { y.store(true, std::memory_order_seq_cst); }void read_x_then_y()
{while(!x.load(std::memory_order_seq_cst));if(y.load(std::memory_order_seq_cst)) ++z;
}void read_y_then_x()
{while(!y.load(std::memory_order_seq_cst));if(x.load(std::memory_order_seq_cst)) ++z;
}int main()
{x = false;y = false;z = 0;std::thread a(write_x);std::thread b(write_y);std::thread c(read_x_then_y);std::thread d(read_y_then_x);a.join();b.join();c.join();d.join();assert(z.load() != 0); // 永远不会触发
}

自由序

自由序只保证原子操作的原子性,不提供任何顺序保证:

#include <atomic>
#include <thread>
#include <assert.h>std::atomic<bool> x, y;
std::atomic<int> z;void write_x_then_y()
{x.store(true, std::memory_order_relaxed);  // 1y.store(true, std::memory_order_relaxed);  // 2
}void read_y_then_x()
{while(!y.load(std::memory_order_relaxed));  // 3if(x.load(std::memory_order_relaxed)) ++z;  // 4
}int main()
{x = false;y = false;z = 0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load() != 0); // 可能触发!
}

获取-释放序

获取-释放序提供了比自由序更强的同步保证:

#include <atomic>
#include <thread>
#include <assert.h>std::atomic<bool> x, y;
std::atomic<int> z;void write_x_then_y()
{x.store(true, std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_release);  // 释放栅栏y.store(true, std::memory_order_relaxed);
}void read_y_then_x()
{while(!y.load(std::memory_order_relaxed));std::atomic_thread_fence(std::memory_order_acquire);  // 获取栅栏if(x.load(std::memory_order_relaxed)) ++z;
}int main()
{x = false;y = false;z = 0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load() != 0); // 不会触发
}

5.3.4 释放序列与同步

释放序列确保了一系列原子操作的正确同步:

#include <atomic>
#include <thread>
#include <vector>std::vector<int> queue_data;
std::atomic<int> count;void populate_queue()
{unsigned const number_of_items = 20;queue_data.clear();for(unsigned i = 0; i < number_of_items; ++i){queue_data.push_back(i);}count.store(number_of_items, std::memory_order_release);
}void process(int item) { /* 处理数据 */ }void consume_queue_items()
{while(true){int item_index;if((item_index = count.fetch_sub(1, std::memory_order_acquire)) <= 0){continue; // 等待更多项目}process(queue_data[item_index - 1]);}
}int main()
{std::thread a(populate_queue);std::thread b(consume_queue_items);std::thread c(consume_queue_items);a.join();b.join();c.join();
}

5.3.5 栅栏

内存栅栏提供了对内存操作顺序的强制约束:

#include <atomic>
#include <thread>
#include <assert.h>std::atomic<bool> x, y;
std::atomic<int> z;void write_x_then_y()
{x.store(true, std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_release);y.store(true, std::memory_order_relaxed);
}void read_y_then_x()
{while(!y.load(std::memory_order_relaxed));std::atomic_thread_fence(std::memory_order_acquire);if(x.load(std::memory_order_relaxed)) ++z;
}int main()
{x = false;y = false;z = 0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load() != 0); // 不会触发
}

5.3.6 原子操作对非原子操作排序

原子操作也可以对非原子操作进行排序:

#include <atomic>
#include <thread>
#include <assert.h>bool x = false; // 非原子变量
std::atomic<bool> y;
std::atomic<int> z;void write_x_then_y()
{x = true; // 非原子写入std::atomic_thread_fence(std::memory_order_release);y.store(true, std::memory_order_relaxed);
}void read_y_then_x()
{while(!y.load(std::memory_order_relaxed));std::atomic_thread_fence(std::memory_order_acquire);if(x) ++z; // 读取非原子变量
}int main()
{y = false;z = 0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load() != 0); // 不会触发
}

5.3.7 非原子操作排序

非原子操作可以通过原子操作进行排序,这是更高级同步工具的基础:

#include <atomic>
#include <thread>class spinlock_mutex
{std::atomic_flag flag;
public:spinlock_mutex() : flag(ATOMIC_FLAG_INIT) {}void lock(){while(flag.test_and_set(std::memory_order_acquire));}void unlock(){flag.clear(std::memory_order_release);}
};spinlock_mutex mutex;
int shared_data = 0;void worker()
{mutex.lock();++shared_data; // 受保护的操作mutex.unlock();
}int main()
{std::thread t1(worker);std::thread t2(worker);t1.join();t2.join();return 0;
}

C++标准库提供了多种同步机制,包括互斥量、条件变量、future等,它们都基于这些基本的内存序概念构建,为多线程编程提供了更高级的抽象。

理解这些内存序概念对于编写正确高效的多线程程序至关重要。在实际开发中,应该优先使用高级同步工具,只有在需要极致性能时才考虑直接使用原子操作和内存序。

http://www.xdnf.cn/news/19927.html

相关文章:

  • 理解Go与Python中的闭包(Closure)
  • 充电枪结构-常规特征设计
  • 代码随想录刷题Day48
  • PostgreSQL 索引使用分析2
  • 权威认证!华宇TAS应用中间件获得商用密码产品认证证书
  • 深入解析Go语言切片(Slice)精髓
  • 【论文阅读】LightThinker: Thinking Step-by-Step Compression (EMNLP 2025)
  • 金额字段该怎么设计?——给小白的超详细指南(含示例 SQL)
  • UniApp 混合开发:Plus API 从基础到7大核心场景实战的完整指南
  • 一文吃透 Protobuf “Editions” 模式从概念、语法到迁移与实战
  • 自动化仓库托盘搬运减少错误和损坏的方法有哪些?实操案例解读
  • 【踩坑记录】Unity 项目中 PlasticSCM 掩蔽列表引发的 文件缺失问题排查与解决
  • 分割回文串手绘图
  • 【OpenGL】LearnOpenGL学习笔记19 - 几何着色器 Geometry Shader
  • 解决 Android Studio 中 build 目录已被 Git 跟踪后的忽略问题
  • 【stm32】定时器中断与定时器外部时钟
  • el-table 行高亮,点击行改变背景
  • CVE-2025-6507(CVSS 9.8):H2O-3严重漏洞威胁机器学习安全
  • 安全测试漫谈:如何利用X-Forwarded-For头进行IP欺骗与防护
  • TDengine NOW() 函数用户使用手册
  • Ubuntu环境下的 RabbitMQ 安装与配置详细教程
  • RabbitMQ篇
  • 20250903的学习笔记
  • LangChain实战(十三):Agent Types详解与选择策略
  • 动态IP和静态IP配置上有什么区别
  • 单片机控制两只直流电机正反转C语言
  • 如何保存训练的最优模型和使用最优模型文件
  • 【wpf】WPF开发避坑指南:单例模式中依赖注入导致XAML设计器崩溃的解决方案
  • SpringBoot注解生效原理分析
  • AI落地新趋势:美林数据揭示大模型与小模型的协同进化论