计算机原理-原子操作,内存屏障,锁总结

本文深入探讨了计算机系统中同步机制的原理,包括原子操作、内存屏障、锁等关键概念,以及它们如何在不同CPU架构下确保数据的一致性和避免异步问题。详细解释了锁的多种类型及其应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

新博客地址(shankusu.me)

原理

  • CPU提供了原子操作、关中断、锁内存总线,内存屏障等机制;OS基于这几个CPU硬件机制,就能够实现锁;再基于锁,就能够实现各种各样的同步机制(信号量、消息、Barrier等等等等)。

  • 所有的同步操作最基础的理论就是原子操作。内存屏障,锁都是为了保证在不同的平台或者是CPU类型下的原子操作。

  • 原子操作在单核,单线程/无中断,且编译器不优化的情况下是确定的,是按照C/C++代码顺序执行的,所以不存在异步问题。

为了能够理解异步操作产生的原因,我们先来了解下CPU处理的过程:

1.先启动的处理器为有序处理器,指令处理顺序:

  • a.读取指令

  • b.执行指令如果寄存器可写就从内存取出a的数据到寄存器,寄存器不可写就等待

  • c.寄存器处理指令

  • d.将寄存器结果存入内存

2.现在的处理器大多数为乱序处理器,处理顺序:

  • a.读取指令

  • b.指令被划分到指令队列

  • c.指令在队列中等待,如果寄存器可写就从内存取出a的数据到寄存器,寄存器不可写就等待

  • d.寄存器处理指令

  • e.将执行结果存入队列(而不是立即写入寄存器堆)

  • f.只有当所有更早的请求执行的指令结果被写入内存之后,执行的结果才会被存入内存(执行结果重排序,让执行看起来是有序的)

那么问题来了:

1.一条简单的a++语句究竟会有这么多条指令,而这一组指令是可以在任意时候异步执行的(共享数据)

  • a.单核多线程情况下,线程是存在中断的,中断的时候cpu调用另一线程的同一指令组,所以是可能出现交叉执行的可能,也就是说单线程或者关掉中断可以解决异步问题,但很多时候这种做法并不实际。

  • b.多核多线程情况下共享数据被多个核并行处理,不论哪一种处理器都存在同时执行的可能,这就导致了异步问题。

  • c.现在的编译器都具有优化及自动优化功能,优化之后可能会对共享变脸的访问顺序进行调整,可能会造成与预期不相符的结果。

内存屏障的作用

  • a.在编译时:拒绝编译器优化屏障前后的指令,防止内存乱序访问。

  • b.在运行时:告诉内存地址总线共享数据地址的数据必须同步(当多个线程同时将一个共享数据地址的数据加载到队列里的时候,先完成处理从cpu到内存的时候总是通知其他线程跟新队列中的该共享数据,从而保证一致性)。

常用场合

  • 1.实现同步原语(synchronization primitives)

  • 2.实现无锁数据结构(lock-free data structures)

  • 3.驱动程序

从上面可以看出内存屏障并不是锁,而锁是使用了内存屏障实现的一种用户层的同步处理方式,锁使用的汇编原语有LOCK,UNLOCK等是内存屏障的一种隐式形式,它们都是LOCK操作和UNLOCK操作的变种,所以几乎所有的锁都使用了内存屏障。

锁的种类

  • 原子锁:使用了锁总线的方式实现原子操作

  • 自旋锁:while等待,不可抢占的单CPU内核下是无效的,有软中断的情况下,必须使用时本地软中断失效的方法。自旋锁更像是一种用户层控制的while等待处理

  • 读写锁: 读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作

  • 互斥锁:沉睡/休眠等待,所以互斥锁比自旋锁调度耗时。

  • 信号量:用于同一时刻有多个个实例能获取锁,可用于表示同时有多少个client请求允许访问同一个数据块,允许锁个数设置为1的时候就是互斥锁.

  • 读写信号量:对同时拥有的读者数不受限制,只能一个写者,写者发现不需要写的时候降级为读者。

  • 顺序锁:用于能够区分读与写的场合,并且是读操作很多、写操作很少,写操作的优先权大于读操作。

  • 读拷贝锁:RCU(read-copy-update)(RCU也是用于能够区分读与写的场合,并且也是读多写少,但是读操作的优先权大于写操作)

### C++ 原子操作原理与 `std::atomic` 的内部实现 #### 背景介绍 在多线程环境下,多个线程可能同时访问共享资源。为了防止竞争条件(race condition),需要一种机制来确保某些操作是不可中断的。这种不可中断的操作被称为 **原子操作**。C++ 提供了标准库中的 `std::atomic` 类模板来支持这些原子操作。 #### 原子操作的核心概念 原子操作是指不会被其他线程干扰的一组指令序列,在执行过程中无法被打断。这意味着即使有多个线程试图在同一时间修改同一个变量,也不会发生不一致的状态[^2]。 #### `std::atomic` 的基本功能 `std::atomic<T>` 是一个模板类,用于定义具有原子性质的对象。它的主要成员函数包括但不限于: - `load()` 和 `store()`:分别用来读取和写入值。 - `exchange()`:交换当前值并返回旧值。 - `compare_exchange_weak/strong()`:比较并替换值,如果匹配则更新,否则保持原值不变。 下面是一个简单的例子展示如何使用 `std::atomic<int>` 进行加载和存储操作: ```cpp #include <iostream> #include <atomic> std::atomic<int> atomicInt(0); void loadStoreExample() { int value = atomicInt.load(); // 加载值 std::cout << "Loaded Value: " << value << '\n'; atomicInt.store(42); // 存储新值 std::cout << "Stored New Value: " << atomicInt.load() << '\n'; } ``` #### 内部实现细节 `std::atomic` 的具体实现依赖于底层硬件的支持以及编译器的能力。通常情况下,它会利用处理器提供的特殊指令集来进行同步控制。例如: 1. **内存屏障(Memory Fence/Memory Barrier)**: 防止编译器优化或 CPU 乱序执行破坏程序逻辑顺序。 2. **CAS 操作(Compare And Swap)**: 实现无算法的基础之一,允许在一个单一步骤中完成比较和赋值动作。 对于大多数现代架构来说,像 x86/x64 平台上的汇编指令 CMPXCHG 就是用来做 CAS 动作的好帮手;而在 ARM 架构下,则可能是通过 LDREX/STREX 对应的负载链接存储释放对来达成相似效果[^4]。 当涉及到跨平台开发时,不同体系结构间可能存在差异,因此 STL 库开发者们精心设计了一套抽象层封装住这些复杂度,让用户无需关心太多低级层面的东西即可享受高效能且可靠的并发编程体验。 #### 总结 综上所述,`std::atomic` 不仅简化了程序员编写多线程应用的工作量,而且极大地提高了代码的安全性和可维护性。其背后隐藏着丰富的理论基础和技术手段,从理论上讲涵盖了计算机组成原理操作系统再到高级语言特性等多个领域知识点[^3]. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值