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 ...
随机推荐
- in与exist , not in与not exist 的区别
in和exists in 是把外表和内表作hash 连接,而exists是对外表作loop循环,每次loop循环再对内表进行查询.一直以来认为exists比in效率高的说法是不准确的. ...
- 在MVC模式下通过Jqgrid表格操作MongoDB数据
看到下图,是通过Jqgrid实现表格数据的基本增删查改的操作.表格数据增删改是一般企业应用系统开发的常见功能,不过不同的是这个表格数据来源是非关系型的数据库MongoDB.nosql虽然概念新颖,但是 ...
- 杀入红海市场 ZUK手机底气在哪?
从越来越奢华的发布会舞台屏幕,到创意越来越烧脑的邀请函,一款新手机的发布工作变得越来越系统化.何时展示.如何亮相,都成为影响一部手机情怀,甚至销售好坏的重要因素.虽然很难以一个固定标准衡量各个手 ...
- 在线做RAID命令
# 安装raid卡管理工具 wget http://10.12.30.102:10800/other/MegaCli-8.07.14-1.noarch.rpm -O /tmp/MegaCli-8.07 ...
- iPhone5se难逃“酱油”命运?
苹果春季新品发布会即将举行,按照惯例,只会有一些不痛不痒的产品出现,最起码,不会有革命性的爆点,今年大抵相似,最大的亮点莫过于,苹果有可能会推出一款名叫"iPhone5se"的手机 ...
- 关于struct stat
需要使用struct stat 类型时如果编译不过,修改Makefile: ##CFG_INC := -I$(MPI_DIR)/api/so/##CFG_INC += -I$(BASE_DIR)/pu ...
- Neural Turing Machine - 神经图灵机
Neural Turing Machine - 神经图灵机 论文原文地址: http://arxiv.org/pdf/1410.5401.pdf 一般的神经网络不具有记忆功能,输出的结果只基于当前的输 ...
- flask-restful 初探
flask-restful 是 Flask 的一个用于支持 RESTful 的插件. 刚开始用对我来说还是比较坑的... 目录结构 / /test /test/common /__init__.py ...
- OpenFlow(OVS)下的“路由技术”
前言 熟悉这款设备的同学,应该也快到不惑之年了吧!这应该是Cisco最古老的路由器了.上个世纪80年代至今,路由交换技术不断发展,但是在这波澜壮阔的变化之中,总有一些东西在嘈杂的机房内闪闪发光,像极了 ...
- PYTHON程序设计实验
Python程序设计实验报告一: 熟悉IDLE和在线编程平台 安徽工程大学 Python程序设计实验报告 班级 物流191 姓名 邹缕学号 3190505117成绩 ▁▁▁ 日期 2020.3.5 指 ...