深入理解Memory Order
深入理解Memory Order
- cpu 保证
- cache
- 编程技术
- lock-free
- wait-free
- Read–modify–write
- Compare-And-Swap(CAS)
- cas原理
- cas开销
- test-and-set
- consensus member
- wait-free
- ABA problem
- memory order
- 背景知识
- 延伸:`__asm volatile("" ::: "memory")`的含义
- memory model
- C++ atomic
- memory_order
- memory_order_relaxed
- memory_order_consume
- memory_order_acquire
- memory_order_release
- memory_order_acq_rel
- memory_order_seq_cst
- 术语
- **Sequenced-before**
- Carries dependency
- Modification order
- Release sequence
- Synchronizes-with
- Dependency-ordered before
- **Inter-thread happens-before**
- Happens-before
- Visible side-effects
- 编程实现
- 操作组合完成原子操作
- fense
- atomic_thread_fence分类和效果
- fence和同样memory order的原子操作同步效果的区别
- 利用fense进行同步
- 费曼方法
- linux kernel memory barriers阅读笔记
- 我们为什么要学习原子操作和弱内存序
- Cacheline
- 参考链接
cpu 保证
intel SDM Volume 3 8.1.1 Guaranteed Atomic Operations谈到了,在intel平台了,有一些内存操作是被处理器天然的保证原自性,不需要lock
cache
编程技术
lock-free
Lock free允许单独的线程个体阻塞,但是会保证系统整体上的吞吐,如果一个算法对应的程序的线程在运行了足够长时间的情况下,至少有一个线程取得了进展,那么我们说这个算法是lock-free的。
概括起来就是,如果涉及到共享内存的多线程代码在多线程执行下不可能互相影响导致被hang住,不管OS如何调度线程,至少有一个线程在做有用的事,那么就是lock-free。使用了锁的代码肯定不是lock free,因为一个线程加锁后如果被系统切出去了其他所有线程都处于等待中。但是没用锁也不一定是lock free,**因为普通的代码逻辑也可能会导致一个线程hang住另一个线程。**锁之所以在高并发的时候表现很差,主要原因就是加锁的线程会hang住其他待加锁的线程,lock-free可以很好的解决这一问题。
wait-free
Read–modify–write
read modify write是一系列原子操作,比如 cas, fetchadd, test-and-set
Compare-And-Swap(CAS)
CAS是乐观锁并且可能失败,读写锁是悲观锁
类似c++11里的compare_exchange_strong
cas原理
比较再交换, CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
如两个线程去更改一个内存地址,则只会有一个成功。失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试
从粒度上而言,cas只能保证一个共享变量的原子性,如果对多个共享变量操作,要加锁了
cas开销
CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。但会有cache miss的开销
- 当 CPU 从内存中读取一个变量到它的寄存器中时,必须首先将包含了该变量的缓存线读取到 CPU 高速缓存。同样地,CPU 将寄存器中的一个值存储到内存时,不仅必须将包含了该值的cacheline读到 CPU 高速缓存,还必须确保没有其他 CPU 拥有该cacheline的拷贝。
test-and-set
textandset就是传入两个值与一个内存地址,让比较值与内存地址中的值相比较,如果一样就交换,不一样就不交换。返回值是内存地址中的值,整个过程是原子的。通常用于实现自旋锁
consensus member
Maurice Herlihy (1991) ranks atomic operations by their consensus numbers, as follows:
- ∞: memory-to-memory move and swap, augmented queue, compare-and-swap, fetch-and-cons, sticky byte, load-link/store-conditional (LL/SC)[1]
- 2n - 2: n-register assignment
- 2: test-and-set, swap, fetch-and-add(FAA), queue, stack
- 1: atomic read and atomic write
从上面可以看出, cas是无穷,fetch and add 是2
wait-free
single-reader-single-writer queue
ABA problem
如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?
如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本(加一个version字段)来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。
memory order
memory order受编译器和cpu运行时重排的影响。c++ 11后也正式引入了memory order的概念,c++的memory oder更多则是一种约定和方法,给程序员提供了一种跨平台的通用方法来限制上述两种重排,这也极大地方便了我们实现lock free算法,在此之前,程序员如果想要限制重排,可能需要手动调用不同平台的专有fence。
背景知识
- cpu优化
- X86/64属于强memory model的范畴,只可能发生Storeload reorder。原来在写操作之后的读操作重排到之前,并且Intel 64 Architectures Software Developer’s Manual, Volume 3, section 8.2.2指出,Reads may be reordered with older writes to different locations but not with older writes to the same location.
- arm架构属于弱memory model,四种重排都会发生,包括
Loadload reorder
、Loadstore reorder
、Storeload reorder
、Storestore reorder
,只不过arm保证了数据依赖顺序,在 Arm® Architecture Reference Manual Armv8, for Armv8-A architecture profile B2.2.3中提到:
- All writes to the same location are serialized, meaning they are observed in the same order by all observers, although some observers might not observe all of the writes.
- A read of a location does not return the value of a write until all observers observe that write.
- 编译器优化:
- LINUX KERNEL MEMORY BARRIERS其中有个章节叫做
Compiler barrier
,主要讲编译器对memory order做出的优化,很多在单线程编程(uniprocessor)中没有任何问题的优化,在并发编程(concurrent code)中会导致致命的错误,所以很多地方需要加编译器屏障
来显示屏蔽编译器优化。 - 其中linux kernel中主要使用
READ_ONCE()
和WRITE_ONCE()
对单条指令增加volatile
语义,也可以对变量增加volatile
定义给其增加volatile
语义。 - 另外也可以使用
barrier()
显式的增加编译器内存屏障,其作用相当于G++中的__asm volatile("" ::: "memory")
- LINUX KERNEL MEMORY BARRIERS其中有个章节叫做
延伸:__asm volatile("" ::: "memory")
的含义
- 这条语句后面的语句不能被编译器优化到前面去,前面的不能被优化到后面去。这是所有内嵌汇编都具有的效果。
- 所在函数不得被inline,不管开多高的优化等级, 这也是所有内嵌汇编都具有的效果。但是这一点目前也渐渐被gcc新版本打脸了。。
- 重点,这条语句之后,函数返回前的所有流程(也就是包括了循环语句 跳转到这条语句前面去的场景),对所有地址的访问(包括局部变量地址)都必须去内存里去load,而不能使用这条语句前寄存器缓存的结果(当然,某些寄存器的结果编译器还是认为还是有效的,例如堆栈寄存器,不然代码没法走下去了)。也就是这条语句后的代码都含有volatile的语义。
本节摘自朱元的知乎回答
memory model
对内存的操作可以概括为读和写,也就是load和store,因此reorder也就可以整体上分为以下四种类型:
- Loadload reorder:两个读操作之间重排
- Loadstore reorder:原来在写操作之前的读操作重排到之后
- Storeload reorder:原来在读操作之前的写操作重排到之后
- Storestore reorder:两个写操作之间重排
X86/64(intel/AMD)属于强memory model的范畴,只可能发生Storeload reorder。原来在读操作之前的写操作重排到之后,并且Intel 64 Architectures Software Developer’s Manual, Volume 3, section 8.2.2指出,Reads may be reordered with older writes to different locations but not with older writes to the same location.
C++ atomic
#include <atomic>
如果使用atomic但是不用任何的成员函数,而是用自增,+=之类的操作符,那么相当于默认使用fetch_add,默认使用order:std::memory_order_seq_cst
memory_order
typedef enum memory_order {memory_order_relaxed,memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_rel,memory_order_seq_cst
} memory_order;
memory_order_relaxed
这个很好理解,宽松的内存序,指定为这个的所有原子操作就真的仅仅是保证自身的原子性,不会对任何其他变量的读写产生影响。如果确实不需要其他的内存同步,那么这是最好的选择,比如原子计数器。
memory_order_consume
memory_order_consume适用于load operation,对于采用此内存序的load operation,我们可以称为consume operation,设有一个原子变量M上的consume operation,对周围内存序的影响是:当前线程中该consume operation后的依赖该consume operation读取的值的load 或strore不能被重排到该consume operation前,其他线程中所有对M的release operation及其之前的对数据依赖变量的写入都对当前线程从该consume operation开始往后的操作可见,相比较于下面讲的memory_order_acquire,memory_order_consume只是阻止了之后有依赖关系的重排。绝大部分平台上,这个内存序只会影响到编译器优化,依赖于dependency chain。但实际上很多编译器都没有正确地实现consume,导致等同于acquire。
memory_order_acquire
memory_order_acquire适用于load operation,对于采用此内存序的load operation,我们可以称为acquire operation,设有一个原子变量M上的acquire operation,对周围内存序的影响是:当前线程中该acquire operation后的load 或strore不能被重排到该acquire operation前,其他线程中所有对M的release operation及其之前的写入都对当前线程从该acquire operation开始往后的操作可见。
memory_order_release
memory_order_release适用于store operation,对于采用此内存序的store operation,我们可以称为release operation,设有一个原子变量M上的release operation,对周围内存序的影响是:该release operation前的内存读写都不能重排到该release operation之后。并且:
- 截止到该release operation的所有内存写入都对另外线程对M的acquire operation以及之后的内存操作可见,这就是release acquire 语义。
- 截止到该operation的所有M所依赖的内存写入都对另外线程对M的consume operation以及之后的内存操作可见,这就是release consume语义。
memory_order_acq_rel
memory_order_acq_rel适用于read-modify-write operation,对于采用此内存序的read-modify-write operation,我们可以称为acq_rel operation,既属于acquire operation 也是release operation. 设有一个原子变量M上的acq_rel operation:自然的,该acq_rel operation之前的内存读写都不能重排到该acq_rel operation之后,该acq_rel operation之后的内存读写都不能重排到该acq_rel operation之前. 其他线程中所有对M的release operation及其之前的写入都对当前线程从该acq_rel operation开始的操作可见,并且截止到该acq_rel operation的所有内存写入都对另外线程对M的acquire operation以及之后的内存操作可见。
memory_order_seq_cst
memory_order_seq_cst 可以用于 load operation,release operation, read-modify-write operation三种操作,用于 load operation的时候有acquire operation的特性,用于 store operation的时候有release operation的特性, 用于 read-modify-write operation的时候有acq_rel operation的特性,除此之外,有个很重要的附加特性,一个单独全序,也就是所有的线程会观察到一致的内存修改,也就是顺序一致性的强保证。参考链接17讲到了memory_order_seq_cst
和memory_order_acq_rel
的不同
术语
C++ memory order循序渐进(二)—— C++ memory order基本定义和形式化描述所需术语关系详解
熟悉了各种原子操作,这些术语就不难理解了
Sequenced-before
这个定义的是同一个线程内的一种根据表达式求值顺序来的一种关系,完整的规则定义很复杂,可以参考http://en.cppreference.com/w/cpp/language/eval_order,其中最直观常用的一条规则简单来说如下:每一个完整表达式的值计算和副作用都Sequenced-before于下一个完整表达式的值计算和副作用。从而也就有以分号结束的语语句Sequenced-before于下一个以分号结束的语句,比如:
r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D
从而有 C Sequenced-before D。
Carries dependency
Modification order
Release sequence
Synchronizes-with
Dependency-ordered before
这个关系是针对consume来的,对于分属不同线程的赋值A和赋值B,如果他们之间有以下关系之一:
- A对某个原子变量M做release操作,B对M做consume操作,并且B读到了release sequence headed by A中的任意一个值。这条就是上面说的release sequence rule的关键。
- A is dependency-ordered before X,并且 X carries dependency into B。前面说过了,carries dependency into是线程内的一种关系,这里就是X和B属于同一线程,并且B对X有依赖。
我们就说 A dependency-ordered before B。
Inter-thread happens-before
Inter-thread happens-before,看字面意思就知道是定义了线程间赋值操作之间的关系,假如有线程1中的赋值操作A,线程2中的赋值操作B,如果满足以下任意一条,那么有A inter-thread happens before B:
- A synchronizes-with B
- A dependency-ordered before B
- A synchronizes-with 某个 evaluation X, 并且 X sequenced-before B
- A sequenced-before 某个 evaluation X, and X inter-thread happens-before B
- A inter-thread happens-before 某个evaluation X,并且X inter-thread happens-before B
Happens-before
Visible side-effects
编程实现
要达到数据同步的效果,需要进行组合使用,我们可以在两个地方指定memory order,一是atomic变量(原子变量)的操作,二是fence,利用fence可以不依赖原子变量进行同步
操作组合完成原子操作
- Relaxed ordering,所有的都是relaxed,没有序的保证
- Release-Acquire ordering 线程A使用release, 线程B使用acquire,所有happened-before A的内存写入都对线程B acquire的操作可见
- Release-Consume ordering 因为Release-Consume需要跟踪依赖链,目前在很多编译器实现上是直接等同于Release-Acquire的,并且从c++17标准开始, release-consume order还在修订,并不推荐使用。
- Sequentially-consistent ordering 使用memory_order_seq_cst可以获得全局顺序一致,是atomic变量默认的内存序,也是最强的保证,在如果形成了该内存序,除了有release acquire的效果,还会外加一个单独全序,也就是保证所有的线程观察到内存操作完全同样的顺序。,如果对性能有较高要求,不是特别必要不要使用这个内存序。
仔细想想,原子操作其实存在两个语义,一个是对当前线程限制重排,另一个是synchronized-with另外的线程
如图,那种没有synchronized-with的关系,不会有visble side-effect
fense
#include <atomic>
extern "C" void atomic_thread_fence( std::memory_order order ) noexcept;
fence不光可以不依赖原子操作进行同步,而且相比较于同样memory order的原子操作,具有更强的内存同步效果。无论是纯粹基于原子操作的同步,还是利用fence的,最常用的两种就是release acquire和Sequentially-consistent ordering
atomic_thread_fence分类和效果
和atomic变量类似,atomic_thread_fence也可以指定六种memory order,指定不同memory order的fence可以分为以下几类:
std::atomic_thread_fence(memory_order_relaxed)
,没有任何效果。std::atomic_thread_fence(memory_order_acquire)
和std::atomic_thread_fence(memory_order_consume)
属于acquire fence。std::atomic_thread_fence(memory_order_release)
属于release fence。std::atomic_thread_fence(memory_order_acq_rel)
既是acquire fence 也是release fence,为了方便这里称为full fence。std::atomic_thread_fence(memory_order_seq_cst)
额外保证有单独全序的full fence。
fence和同样memory order的原子操作同步效果的区别
基于atomic_thread_fence(外加一个任意序的原子变量操作)的同步和基于原子操作的同步很类似,比如最常用的,都可以形成release acquire语义,但是从上面的描述可以看出,fence的效果要比基于原子变量的效果更强,在weak memory order平台的开销也更大。
以release为例,对于基于原子变量的release opration,仅仅是阻止前面的内存操作重排到该release opration之后,而release fence则是阻止重排到fence之后的任意store operation之后,比如一个简单的例子:
std::string* p = new std::string("Hello");
ptr.store(p, std::memory_order_release);
以下代码具有同样效果:
std::string* p = new std::string("Hello");
std::atomic_thread_fence(memory_order_release);
ptr.store(p, std::memory_order_relaxed);
再比如:
依赖ptr1的线程永远能读到正确值,但是依赖ptr2的不一定。
std::string* p = new std::string("Hello");
ptr1.store(p, std::memory_order_release);
ptr2.store(p, std::memory_order_relaxed);
依赖ptr1和ptr2的的线程都永远能读到正确值
std::string* p = new std::string("Hello");
std::atomic_thread_fence(memory_order_release);
ptr1.store(p, std::memory_order_relaxed);
ptr2.store(p, std::memory_order_relaxed);
利用fense进行同步
费曼方法
- 原子操作首先根据memoryorder指定了当前线程的内存操作重排
- 不同atomic/fense的组合,指定了不同线程间的同步方式(内存序)
- 最重要的两种内存序:Relase-Acquire ordering 和 Sequentially-consisent ordering
- fense的效果比atomic要更好,但是开销更大。release fence则是阻止重排到fence之后的任意store operation之后
linux kernel memory barriers阅读笔记
- linux上一般使用smp barrier。这种barrier在up系统的一般情况下上会退化为compile barrier
- linux系统没有上述高级语言的这么多种关系名词,但是两个线程形成的关系和高级语言的的关系大概相同
- 内核里的READ_ONCE()和WRITE_ONCE()只对非
volatile
生效,volatile
本身已经包含READ_ONCE()和WRITE_ONCE()的volatile语义
我们为什么要学习原子操作和弱内存序
本节摘自文章关于原子操作和弱内存序
有人说这些东西是不是太底层了,平时是不是用不到这些知识。其实不是,除了上面所说的无锁编程场景之外,至少以下几种场景还是要用到的。
- C和C++多线程编程,如果不了解弱内存序的知识,还是很可能会产生一些bug的,而且这些bug都很难定位。
- 定位由于弱内存序产生的bug,即使是业界知名的软件,比如mysql等一些软件,在兼容ARM的过程中,由于弱内存序的问题,也会有很多bug,定位这些bug,也需要这方面的相关知识。
- 如果一些软件一开始只支持了X86,并且使用了底层的原子操作,你如果想要将这些软件兼容ARM,并进行相关开发工作的话,你就要对这些原子操作汇编指令等等进行对应的移植,还要优化。这个时候也需要这方面的知识。比如我们团队在移植Impala过程中的相关开发工作
- 有时候有些软件的内存序使用过于严格,也需要进行优化,会对软件的性能有一定的提升。比如我们团队大牛在mysql上的一些优化工作,就是针对这方面的,比如有些原子变量的自增,并不需要依赖上下文,所以就可以不需要严格的内存屏障语义。可以参考:
https://bugs.mysql.com/bug.php?id=99432
https://bugs.mysql.com/bug.php?id=100119
https://bugs.mysql.com/bug.php?id=100432
https://bugs.mysql.com/bug.php?id=100060
https://bugs.mysql.com/bug.php?id=100132
Cacheline
没有任何竞争或只被一个线程访问的原子操作是比较快的,“竞争”指的是多个线程同时访问同一个cacheline。现代CPU为了以低价格获得高性能,大量使用了cache,并把cache分了多级。百度内常见的Intel E5-2620拥有32K的L1 dcache和icache,256K的L2 cache和15M的L3 cache。其中L1和L2 cache为每个核心独有,L3则所有核心共享。一个核心写入自己的L1 cache是极快的(4 cycles, ~2ns),但当另一个核心读或写同一处内存时,它得确认看到其他核心中对应的cacheline。对于软件来说,这个过程是原子的,不能在中间穿插其他代码,只能等待CPU完成一致性同步,这个复杂的硬件算法使得原子操作会变得很慢,在E5-2620上竞争激烈时fetch_add会耗费700纳秒左右。访问被多个线程频繁共享的内存往往是比较慢的。比如像一些场景临界区看着很小,但保护它的spinlock性能不佳,因为spinlock使用的exchange, fetch_add等指令必须等待最新的cacheline,看上去只有几条指令,花费若干微秒并不奇怪。
要提高性能,就要避免让CPU频繁同步cacheline。这不单和原子指令本身的性能有关,还会影响到程序的整体性能。最有效的解决方法很直白:尽量避免共享。
- 一个依赖全局多生产者多消费者队列(MPMC)的程序难有很好的多核扩展性,因为这个队列的极限吞吐取决于同步cache的延时,而不是核心的个数。最好是用多个SPMC或多个MPSC队列,甚至多个SPSC队列代替,在源头就规避掉竞争。
- 另一个例子是计数器,如果所有线程都频繁修改一个计数器,性能就会很差,原因同样在于不同的核心在不停地同步同一个cacheline。如果这个计数器只是用作打打日志之类的,那我们完全可以让每个线程修改thread-local变量,在需要时再合并所有线程中的值,性能可能有几十倍的差别。
一个相关的编程陷阱是false sharing:对那些不怎么被修改甚至只读变量的访问,由于同一个cacheline中的其他变量被频繁修改,而不得不经常等待cacheline同步而显著变慢了。多线程中的变量尽量按访问规律排列,频繁被其他线程修改的变量要放在独立的cacheline中。要让一个变量或结构体按cacheline对齐
参考链接
- 并发编程—CAS(Compare And Swap)
- wait-free是指什么?
- 深入浅出cache写策略
- C++ memory order循序渐进(一)—— 多核编程中的lock free和memory model系列文章,本文很多内容摘自这系列文章
- Read–modify–write wiki
- Consensus (computer science)一致性, 类似于zk集群的选举和数据同步,必须有一半以上同意
- CAS原理
- 什么是乐观锁,什么是悲观锁
- std::atomic::fetch_add
- std::memory_order 本文很多内容翻译自这里
- preshingNB
- The Synchronizes-With Relation
- Fences with non-atomics in C11
- C++ memory order循序渐进(五)—— C++ memory order在编译器和cpu层面的具体实现
- volatile与内存屏障总结 - 知乎
- Does atomic_thread_fence(memory_order_seq_cst) have the semantics of a full memory barrier?
- How do memory_order_seq_cst and memory_order_acq_rel differ?
- LINUX KERNEL MEMORY BARRIERS最近看到里面的章节SMP Barrier Pairing感觉还是讲的不错的
- Symmetric multiprocessingsmp现代计算机,指多核心的cpu, up(uniprocessor)单核cpu。对上条里面的smp的解释
- 6.47.2 Extended Asm - Assembler Instructions with C Expression Operandsgcc中的asm编程指南
- boost对于atomic fense对应的asm语句
- LWN:怕不怕编译器优化让你的代码彻底乱套? Load tearing会在编译器用多个load指令来实现一次数据访问
- 如何理解 C++11 的六种 memory order?陈文礼的回答还是挺好的,我也是看懂了vhost_gate的问题之后才明白。
- X86/GCC memory fence的一些见解作者列举了例子,说明了为什么
Compiler Fence
在某些情况下是难以胜任的。 - 如何使用屏障指令内核中的
[w,r]mb
和smp_[w,r]mb
等等详细介绍 - C/C++ Volatile关键词深度剖析
- 多核Cache一致性缓存一致性 多核Cache一致性由硬件保证,对软件来说是透明的。
- 关于原子操作和弱内存序写的挺好的
- Understanding Memory-Barrier with MySQL EventMutex
深入理解Memory Order相关推荐
- 理解 C++ 的 Memory Order 以及 atomic 与并发程序的关系
为什么需要 Memory Order 如果不使用任何同步机制(例如 mutex 或 atomic),在多线程中读写同一个变量,那么,程序的结果是难以预料的.简单来说,编译器以及 CPU 的一些行为,会 ...
- atomic 内存序_如何理解 C++11 的六种 memory order?
GTHub:高并发编程--多处理器编程中的一致性问题(下)zhuanlan.zhihu.com 4 C++ Memory model 4.0 写在前面 C++ memory order是对atomi ...
- 如何理解Memory leak
有很多情况会导致这一问题,像重复使用的某个结构体/对象,当再次复用时没有清理上一次使用遗留的数据.系统中存在cache,但cache的过期策略设置不得当等等. 大家好,我是小风哥,今天和大家聊一聊内存 ...
- 理解 Memory barrier(内存屏障)无锁环形队列
Memory barrier 简介 程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问.内存乱序访问行为出现的理由是为了提升程序运行时的性能.内存乱序访问主要发生在两 ...
- 理解 Memory barrier(内存屏障)【转】
转自:http://name5566.com/4535.html 参考文献列表: http://en.wikipedia.org/wiki/Memory_barrier http://en.wikip ...
- [译]C++中的内存同步模式(memory order)
C++11 引入了一个有些晦涩的主题: 内存模型,不过一般都只会在需要 Lock-Free 编程时才会遇到,这里翻译一篇相关文章,希望能够给有兴趣的朋友多些参考.原文在这里. 内存模型中的同步模式(m ...
- 理解 Memory barrier(内存屏障)
转自:http://name5566.com/4535.html 参考文献列表: http://en.wikipedia.org/wiki/Memory_barrier http://en.wikip ...
- ARMV8 datasheet学习笔记3:AArch64应用级体系结构之Memory order
1.前言 2.基本概念 Observer 可以发起对memory read/write访问的都是observer; Observability 是一种观察能力,通过read可以感知到别的observe ...
- 看一遍就理解:order by详解
前言 日常开发中,我们经常会使用到order by,亲爱的小伙伴,你是否知道order by 的工作原理呢?order by的优化思路是怎样的呢?使用order by有哪些注意的问题呢?本文将跟大家一 ...
最新文章
- Ubuntu18.04 编译 ncnn
- 意外终止_美国留学本科意外终止怎么办?
- SP2010开发和VS2010专家食谱--第二章节--工作流
- 在同一窗口和同一选项卡中打开URL
- parceljs 中文文档24小时诞生记
- 【论文】2019 年,智能问答(Question Answering)的主要研究方向有哪些?
- 旋转音乐html,css3可控旋转音乐播放按钮
- Flash制作空战游戏
- <2021SC@SDUSC> 开源游戏引擎 Overload 代码模块分析 之 OvGame (一)—— 概况与 .rc 文件
- 微信小程序 自定义底部导航栏(tabBar)
- 明尼苏达计算机科学硕士录取案例,专业42-明尼苏达大学双城分校研究生录取-W同学...
- 2018版苹果开发者设置内购、税务、银行问题
- Mysql 生成随机数字
- 数据结构期末考试错点汇总
- 狂热之下被遗忘的指标—快充倍率
- iOS之身份证的正则校验
- 快讯 I Nexperia 超低电容 ESD 保护二极管保护汽车数据接口
- Python虽然很火,为啥找工作这么难
- 实验室信息化LIMS系统在食品安全生产检测中的应用分析
- 视频无损放大软件Topaz Video Enhance AI for Mac更新啦