atomic的底层实现
atomic操作
在编程过程中我们经常会使用到原子操作,这种操作即不想互斥锁那样耗时,又可以保证对变量操作的原子性,常见的原子操作有fetch_add、load、increment等。
而对于atomic的实现最基础的解释:原子操作是由底层硬件支持的一种特性。
底层硬件支持,到底是怎么样的一种支持?首先编写一个简单的示例代码:
#include <atomic>
int main()
{
std::atomic<int> a;
//a = 1;
a++;
return 0;
}
然后进行编译, 查看编译文件:
g++ -S atomic.cc
cat atomic.s
_ZNSt13__atomic_baseIiEppEi:
.LFB362:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $16, %rsp
.seh_stackalloc 16
.seh_endprologue
movq %rcx, 16(%rbp)
movl %edx, 24(%rbp)
movq 16(%rbp), %rax
movq %rax, -8(%rbp)
movl $1, -12(%rbp)
movl $5, -16(%rbp)
movl -12(%rbp), %edx
movq -8(%rbp), %rax
lock xaddl %edx, (%rax)
movl %edx, %eax
nop
addq $16, %rsp
popq %rbp
ret
.seh_endproc
.ident "GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
我们可以看到在执行自增操作的时候,在xaddl
指令前多了一个lock
前缀,而cpu
对这个lock
指令的支持就是所谓的底层硬件支持。
增加这个前缀后,保证了 load-add-store 步骤的不可分割性。
lock 指令的实现
众所周知,cpu在执行任务的时候并不是直接从内存
中加载数据,而是会先将数据加载到L1
和L2
cache中(典型的是两层缓存,甚至可能更多),然后再从cache中读取数据进行运算。
而现在的计算机通常都是多核处理器,每一个内核都对应一个独立的L1
层缓存,多核之间的缓存数据同步是cpu框架设计的重要部分,MESI
是比较常用的多核缓存同步方案。
当我们在单线程内执行 atomic++
操作,自然是不会发生多核之间数据不同步的问题,但是我们在多线程多核的情况下,cpu是如何保证Lock
特性的?
作者这里以intel x86架构的cpu为例进行说明,首先给出官方的说明文档:
User level locks involve utilizing the atomic instructions of processor to atomically update a memory space.
The atomic instructions involve utilizing a lock prefix on the instruction and having the destination operand assigned to a memory address.
The following instructions can run atomically with a lock prefix on current Intel processors: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG. EnterCriticalSection utilizes atomic instructions to attempt to get a user-land lock before jumping into the kernel. On most instructions a lock prefix must be explicitly used except for the xchg instruction where the lock prefix is implied if the instruction involves a memory address.
In the days of Intel 486 processors, the lock prefix used to assert a lock on the bus along with a large hit in performance.
Starting with the Intel Pentium Pro architecture, the bus lock is transformed into a cache lock.
A lock will still be asserted on the bus in the most modern architectures if the lock resides in uncacheable memory or if the lock extends beyond a cache line boundary splitting cache lines.
Both of these scenarios are unlikely, so most lock prefixes will be transformed into a cache lock which is much less expensive.
上面说明了lock
前缀实现原子性的两种方式:
- 锁bus:性能消耗大,在intel 486处理器上用此种方式实现
- 锁cache:在现代处理器上使用此种方式,但是在无法锁定cache的时候(如果锁驻留在不可缓存的内存中,或者如果锁超出了划分cache line 的cache boundy),仍然会去锁定总线。
大多数人看到这里可能感觉已经懂了,但实际还不够,bus lock
以及多核之间的cache lock
是如何实现的?
The LOCK prefix (F0H) forces an operation that ensures exclusive use of shared memory in a multiprocessor environment.
See “LOCK—Assert LOCK# Signal Prefix” in Chapter 3, “Instruction Set Reference, A-L,” for a description of this prefix
Causes the processor’s LOCK# signal to be asserted during execution of the accompanying instruction (turns the instruction into an atomic instruction). In a multiprocessor environment, the LOCK# signal ensures that the processor has exclusive use of any shared memory while the signal is asserted.
In most IA-32 and all Intel 64 processors, locking may occur without the LOCK# signal being asserted. See the “IA32 Architecture Compatibility” section below for more details.
The LOCK prefix can be prepended only to the following instructions and only to those forms of the instructions where the destination operand is a memory operand: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, CMPXCHG16B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG.
If the LOCK prefix is used with one of these instructions and the source operand is a memory operand, an undefined opcode exception (#UD) may be generated. An undefined opcode exception will also be generated if the LOCK prefix is used with any instruction not in the above list.
The XCHG instruction always asserts the LOCK# signal regardless of the presence or absence of the LOCK prefix.
The LOCK prefix is typically used with the BTS instruction to perform a read-modify-write operation on a memory location in shared memory environment.
The integrity of the LOCK prefix is not affected by the alignment of the memory field. Memory locking is observed for arbitrarily misaligned fields.
This instruction’s operation is the same in non-64-bit modes and 64-bit mode.
在Intel的官方文档上标明,一个LOCK
前缀强制性的确保一个操作在多核环境的shared memory
中操作。LOCK前缀的完整性不受存储字段对齐的影响,对于任意未对齐的字段内存锁定都可以被观察到。
BUS LOCK
这是Intel官方对bus lock的说明
Intel processors provide a LOCK# signal that is asserted automatically during certain critical memory operations to lock the system bus or equivalent link.
While this output signal is asserted, requests from other processors or bus agents for control of the bus are blocked. This metric measures the ratio of bus cycles, during which a LOCK# signal is asserted on the bus.
The LOCK# signal is asserted when there is a locked memory access due to uncacheable memory, locked operation that spans two cache lines, and page-walk from an uncacheable page table.
英特尔处理器提供LOCK#信号,该信号在某些关键内存操作期间会自动断言,以锁定系统总线或等效链接。在断言该输出信号时,来自其他处理器或总线代理的控制总线的请求将被阻止。此度量标准度量总线周期的比率,在此期间,在总线上声明LOCK#信号。当由于不可缓存的内存,跨越两条缓存行的锁定操作以及来自不可缓存的页表的页面遍历而导致存在锁定的内存访问时,将发出LOCK#信号。
在这里,锁定进入操作由总线上的一条消息组成,上面写着“好,每个人都退出总线一段时间”(出于我们的目的,这意味着“停止执行内存操作”)。然后,发送该消息的内核需要等待所有其他内核完成正在执行的内存操作,然后它们将确认锁定。只有在其他所有内核都已确认之后,尝试锁定操作的内核才能继续进行。最终,一旦锁定被释放,它再次需要向总线上的每个人发送一条消息,说:“一切都清楚了,您现在就可以继续在总线上发出请求了”。
CACHE LOCK
cache lock 要比bus lock 复杂很多,这里涉及到内核cache同步,还有 memory barriers
、cache line
和shared memory
等概念,后续会持续更新。
其它
LOCK prefix 不仅仅用于atomic的实现,在其他的一些用户层的同步操作也会应用到,比如依赖于LOCK XCHG
实现自旋锁等。
参考网址
- https://software.intel.com/en-us/articles/implementing-scalable-atomic-locks-for-multi-core-intel-em64t-and-ia32-architectures
- https://zhuanlan.zhihu.com/p/48460953?utm_source=wechat_session&utm_medium=social&utm_oi=61423010971648
- https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf
- https://fgiesen.wordpress.com/2014/08/18/atomics-and-contention/
atomic的底层实现的更多相关文章
- java - CAS及CAS底层原理
CAS是什么? CAS的全称为Compare-And-Swap它是一条CPU并发原语,也就是在CPU硬件层面上来说比较并且判断是否设置新值这段操作是原子性的,不会被其他线程所打断.在JAVA并发包ja ...
- Java锁与CAS
一.加锁与无锁CAS 在谈论无锁概念时,总会关联起乐观派与悲观派,对于乐观派而言,他们认为事情总会往好的方向发展,总是认为坏的情况发生的概率特别小,可以无所顾忌地做事,但对于悲观派而已,他们总会认为发 ...
- java并发编程知识点备忘
最近有在回顾这方面的知识,稍微进行一些整理和归纳防止看了就忘记. 会随着进度不断更新内容,比较零散但尽量做的覆盖广一点. 如有错误烦请指正~ java线程状态图 线程活跃性问题 死锁 饥饿 活锁 饥饿 ...
- 并发一:Java内存模型和Volatile
并发一:Java内存模型和Volatile 一.Java内存模型(JMM) Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和在内存中取出变量的底层细节,是围绕着 ...
- 从原子类和Unsafe来理解Java内存模型,AtomicInteger的incrementAndGet方法源码介绍,valueOffset偏移量的理解
众所周知,i++分为三步: 1. 读取i的值 2. 计算i+1 3. 将计算出i+1赋给i 可以使用锁来保持操作的原子性和变量可见性,用volatile保持值的可见性和操作顺序性: 从一个小例子引发的 ...
- 面试官没想到一个Volatile,我都能跟他扯半小时
点赞再看,养成习惯,微信搜索[三太子敖丙]关注这个互联网苟且偷生的工具人. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的 ...
- 学习一下 SpringCloud (三)-- 服务调用、负载均衡 Ribbon、OpenFeign
(1) 相关博文地址: 学习一下 SpringCloud (一)-- 从单体架构到微服务架构.代码拆分(maven 聚合): https://www.cnblogs.com/l-y-h/p/14105 ...
- Java并发编程--基础进阶高级(完结)
Java并发编程--基础进阶高级完整笔记. 这都不知道是第几次刷狂神的JUC并发编程了,从第一次的迷茫到现在比较清晰,算是个大进步了,之前JUC笔记不见了,重新做一套笔记. 参考链接:https:// ...
- Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。
精彩理解: https://www.jianshu.com/p/21be831e851e ; https://blog.csdn.net/heyutao007/article/details/19 ...
随机推荐
- haproxy笔记之二:HAProxy简介
HAProxy提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费.快速并且可靠的一种解决方案.HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持 ...
- 将js进行到底:node学习5
HTTP开发之Connect工具集--中间件 继学习node.js的TCP API和HTTP API之后,node.js web开发进入了正轨,但这就好像Java的servlet一样,我们不可能使用最 ...
- Redis: userd_memory使用超出maxmemory
Redis:userd_memory使用超出maxmemory 一.问题现象 2018.12.30 19:26分,收到Redis实例内存使用告警“内存使用率299%>=80%”,检查实例info ...
- drbd配置
DRBD就是网络RAID1,可以通过网络同步两个主机上的硬盘,drbd每次只允许对一个节点进行读写访问. 一.安装DRBD CentOS 6.x rpm -ivh http://www.elrepo. ...
- bp(net core)+easyui+efcore实现仓储管理系统——入库管理之二(三十八)
abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...
- Django报Warning错误 RuntimeWarning: DateTimeField Goods.create_at received a naive datetime (2019-07-31 23:05:58) while time zone support is active
报错和UTC(世界标准时间)有关,在settings.py 文件中设置 USE_TZ = False 警告错误不再报
- py面向对象编程基础
'''类:一类事物的抽象,用于定义抽象类型 实例:类的单个实际描述 如:人是一个类,而单个人是一个实例 用class来创建一个类,调用一个类来创建一个实例'''class Person: passxi ...
- 峰哥说技术:03-Spring Boot常用注解解读
Spring Boot深度课程系列 峰哥说技术—2020庚子年重磅推出.战胜病毒.我们在行动 03 Spring Boot常用注解解读 在Spring Boot中使用了大量的注解,我们下面对一些常用的 ...
- Linux系统简单文件操作命令
项目 内容 作业课程归属 班级课程链接 作业要求 作业要求链接 学号-姓名 17041419-刘金林 作业学习目标 1)学习Linux的基本操作命令:2)在终端上运用命令行去实现基本文件操作 1.查看 ...
- 数据库介绍以及MySQL数据库的使用
一 数据库介绍 1.1 数据库定义 数据库就是存储数据的仓库 本质上就是一套cs结构的TCP程序 客户端连接到服务器 向服务器发送指令 完成数据的操作 1.2 常见数据库 关系型数据库 就是 ...