浅谈原子操作

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:阿里云操作系统:子札","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"前言","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f2377d5e16099046179156338a82495.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所谓原子操作,就是要么不做,要么全做。在很多场景中,都有对原子操作的需求。在翻aep的spec文档时,也发现了一个巧妙的方法。所以顺便发散性地总结一下各种实现原子操作的方法,欢迎大家交流探讨。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小粒度——指令","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f2377d5e16099046179156338a82495.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据intel手册卷三第八章的描述,x86使用三种机制来实现原子操作:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. Guaranteed atomic operations。Guaranteed atomic operations是指一些基本的读写内存操作,这些操作都是保证原子性的。一般来说,读写位于一个cache line中的数据是原子性的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. bus lock,使用LOCK#信号和指令的lock前缀。锁总线的方式很简单,进行原子操作的cpu会在bus上assert一个LOCK#信号,此时其他cpu的操作都会被block住。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3. cache lock,利用cache一致性协议(MESI协议)来实现。如果要访问的内存区域已经在当前cpu的cache中了,就会利用cache一致性协议来实现原子操作,否则会锁总线。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"intel早期cpu(如Intel386,Intel486,奔腾处理器)实现原子操作,是通过bus lock来实现的。这种实现的问题,是完全不相关的两个cpu之间,也会相互竞争总线锁,从而导致整体性能下降。在后来的cpu中,intel对这一问题进行了优化。当要进行原子操作的内存已经被拉入cache中时,cpu会使用cache一致性协议来保证原子性,这被称为cache lock。相比于bus lock,cache lock粒度更细,能获得更好的性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"x86中,有些指令是自带lock语义的,比如XCHG,更新段描述符等等;另外一些指令可以手动加上lock前缀来实现lock语义,比如BTS, BTR,CMPXCHG指令。在这些指令中,最核心的当属CAS(Compare And Swap)指令了,它是实现各种锁语义的核心指令。不同于自带原子语义的XCHG,CAS操作要通过\"lock CMPXCHG\"这样的形式来实现。一般而言,原子操作的数据长度不会超过8个字节,也不允许同时对两个内存地址进行CAS操作(如果可以的话,免锁双向链表不是梦)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原子操作中另一个绕不开的话题是ABA问题,水平有限,就不展开讲了。简单提一个例子,在linux内核的slub实现中,用上了一个宏cmpxchg_double,这并不是同时对两个内存地址进行CAS的黑魔法,而正是利用CMPXCHG16B指令解决ABA问题的宏函数,有兴趣的可以深究一把。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"大粒度","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f2377d5e16099046179156338a82495.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当原子操作的对象大小在16字节或者8字节以内时,一两条指令就能实现原子操作。但是,当对象的大小较大时,实现原子操作的就需要其他方法了,比如加锁和COW。深究这两种方法,可以发现在本质上,它们还是将问题转换成了16字节的原子操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"加锁","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加锁这个方式很好理解,只要一加锁,整个临界区的操作就可以被看作一个原子操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"内核中提供了各种各样的锁,自旋锁,读写锁,seq锁,mutex,semaphore等等,这些锁对读写者的倾向各有不同,在是否允许睡眠上也有所不同。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"简单来说,自旋锁和读写锁的核心都是利用原子指令来CAS操纵一个32位/64位的值,它们都不允许睡眠,但是读写锁对于读者做了优化,允许多个读者同时读取数据,而自旋锁则对于读写操作没有什么偏向性。seq基于自旋锁实现,不允许睡眠,但是对写者更为友好。mutex和semaphore也是基于自旋锁实现的,但是它们允许互斥区的操作陷入睡眠。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,加锁这种方式,最核心的还是利用指令实现原子操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"COW","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"针对大对象原子操作的另一种方式是COW(copy on write)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"cow的思想其实非常简单,首先我们有一个指向这个大对象的指针,在需要原子性修改这个大对象的数据时,因为没办法做到inplace修改,所以就把这个对象的数据拷贝一份,在对象副本上修改,最后再原子性地修改指向这个对象的指针。可以看到,这里最核心的地方是利用指令来实现指针的替换。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于COW,这里举一个AEP的例子。AEP是一种存储介质,这里只需要知道它可以按字节寻址和数据在掉电后不消失即可。普通的磁盘,一般有扇区原子性的保证,也就是在将新数据写入某个扇区的途中突然掉电的话,这个扇区上要么完全没有新数据,要么新数据完全被写下去了,不会出现一半新一半旧的状态。扇区原子性的保证很重要,许多数据库都依赖它,然而,AEP这种存储介质没有这种保证,所以需要用软件的方式来做这种保证,称为BTT。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"BTT的思路也很简单,为了方便理解,后文我不引入AEP的术语来进行描述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先把整个存储空间划分成若干个block,每个block有自己的物理块号,然后再维护一个表来做逻辑块号到物理块号的转换。给上层逻辑块的数量略小于物理块数量,这样就会有一部分的物理块没有被映射,姑且称为free block。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如下图,4个逻辑块,5个物理块,其中1号块是free block。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f2/f238436aef4e3d3c7f26cf36e99de5c8.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来,在往一个逻辑块上写数据时,先找一个free block,把数据写上去,接下来去映射表中,将逻辑块的映射修改该free block。整个流程中,最关键的一步——修改映射关系——是原子性的。只要有这个保证,那么就能够提供block数据原子性更新的能力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"COW的思想在很多地方都有,比如qemu的qcow镜像快照,ext4和btrfs在写入数据时的cow,linux内核的rcu机制等等。此外,cow最有名的使用场景莫过于fork的实现了,但是它只是单纯的为了减少拷贝开销,与原子性没有太大关系","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"COW优化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"cow的方式,有个很麻烦的事情,就是每次都得原子性的去更新指针。那么有没有办法去掉这个指针呢?有的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个是在intel关于AEP的文档上学到的另一种取巧的方式(注意,下面描述的例子和上文中的BTT没有任何关系)。起因是这样的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AEP的驱动使用一个称为index block的结构来管理元数据,这个index block处于整个介质的起始位置,大小至少为256字节。有些操作会去更改它的多个字段的值,所以可能出现更改字段到一半的过程中掉电的情况,因此需要一种机制来保证更改过程是原子性的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正常的COW方式,需要在起始位置处保留两个index block大小的空间以及一个指针,其中一个index block作为备用。在修改index block的数据时,以cow的方式将全部的数据存储在备用index block中,然后以COW的方式更改指针指向该备用index block中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Intel使用下面的机制来优化掉指针:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc3bc29e9c74e8398aeaf85737ef0f56.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了方便叙述,两个index block分别命名为blockA和blockB。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一次写入数据,写入到blockA中,其上的seq为01;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二次写入数据,写入到blockB中,其上的seq为10;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三次写入数据,写入到blockA中,其上的seq为11;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第四次写入数据,写入到blockB中,其上的seq为01;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"...","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如此往复,在恢复时,只要读取并比较两个index block上的seq中哪个处于循环的前方,就能找到最新的那个index block。这样的优势是显而易见的,一是避免了额外的指针,或者说把指针固化到两个index block中,避免了一个8字节指针对两个index block对齐带来的麻烦;二是少一次写操作,提升了效率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"多对象","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f2377d5e16099046179156338a82495.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面针对的都是一个个单个的对象,如果涉及到多个对象,要保证原子性就比较复杂了。比如,如果使用加解锁的方式,就需要注意锁的顺序,防止死锁的问题;如果是cow的方式,就需要注意中途失败以后的把已替换的指针回滚回去的问题。从更大的格局来看,针对多个对象的原子操作,本质上就是进行一次事务操作。所以,这个问题的解法,参考事务的实现就好了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"写日志","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f2377d5e16099046179156338a82495.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事务的四大特征ACID,即原子性,一致性,隔离性和持久性,基本上是一个常识了,而原子性只是事务的一个特性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"写日志算是实现事务最通用的方式了,日志一般分为redo和undo两种日志,为了加快恢复速度,一般还会引入检查点(checkpoint)的概念。在文件系统和数据库的实现中,基本上都能看到事务的身影。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"写日志除了能保证原子性和一致性以外,还对磁盘这种外存设备很友好,因为写日志基本上都是顺序的。在这一方面的典型案例,当属日志结构文件系统和leveldb的LSM-tree了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"leveldb的原理想必不用再提了,它把对于K-V对的增删改操作都变成一条条的日志,然后持久化为磁盘上的一个个SST,之后再触发合并整理。这样一来,基本上对于磁盘的所有操作都是顺序的了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"日志结构文件系统也是类似的思想,它将文件数据的增删改操作直接变成日志写到磁盘里面,文件的实际数据不需要单独再存到某个地方,而是靠日志恢复出来。这种做法对写操作是非常友好的,但是读方面的性能就有点差强人意了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"事务内存","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f2377d5e16099046179156338a82495.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事务通常是用于保证持久性数据一致性的。去掉持久性的要求,将事务的概念引入到对于内存对象的操控中,就有了事务内存的概念。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正如上文所说,对于多个对象的操作,加锁和cow的方式,在使用时都比较麻烦。加锁的方式要考虑加解锁顺序防止死锁,中途失败了还要按照特定的顺序解锁回滚;cow也是一样,虽然没有死锁的问题,但是在回滚上也是很麻烦的。另一个问题就是,针对不同的场景,加解锁的顺序要重新考虑,cow的回滚也要重新考虑,不具有通用性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事务内存机制则是为了解决这些问题而提出的,它把针对多个对象的原子操作抽象为一个事务,只要按照它提供的api,以串行化的思路去编程就行了。不用考虑加解锁的顺序,也不必考虑回滚的问题,在遇到了某些fatal error时只要abort掉事务即可。这是一种通用的并发编程方式,简化编码的同时,还能保证并发的性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事实上,事务内存机制的内部实现,也是依赖于cow机制和加解锁来实现的,更深一步,其实也是依赖于原子操作指令的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"总结","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f2377d5e16099046179156338a82495.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"总结一下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"16字节或8字节以内的内存数据,使用cpu的原子操作指令;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"16字节以上的数据,使用加锁、COW的方式,或者优化过的使用seq的COW方式,本质上还是依赖于原子指令;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"针对多个对象的原子操作,引入事务或者事务内存的概念,实际上的实现要么是写日志,要么是依赖于cow或加锁的方式,最终依赖于原子指令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,万变不离其宗,原子操作指令很关键。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"参考链接","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://pmem.io/documents/NVDIMM_Namespace_Spec.pdf","title":null},"content":[{"type":"text","text":"https://pmem.io/documents/NVDIMM_Namespace_Spec.pdf","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://software.intel.com/content/dam/develop/public/us/en/documents/325462-sdm-vol-1-2abcd-3abcd.pdf","title":null},"content":[{"type":"text","text":"https://software.intel.com/content/dam/develop/public/us/en/documents/325462-sdm-vol-1-2abcd-3abcd.pdf","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/151425608","title":null},"content":[{"type":"text","text":"https://zhuanlan.zhihu.com/p/151425608","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E4%BA%8B%E5%8A%A1%E5%86%85%E5%AD%98","title":null},"content":[{"type":"text","text":"https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E4%BA%8B%E5%8A%A1%E5%86%85%E5%AD%98","attrs":{}}]}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更多信息,欢迎钉钉扫描二维码进群交流。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/17/17655dec580e3d3fcf07ab58310102e2.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"往期精彩回顾:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzUxNjE3MTcwMg==&mid=2247484798&idx=1&sn=2f91a6983cf714d938a05493d9b53c3e&chksm=f9aa37a7ceddbeb1fc1ff2e30640ccf05c43314719e8148f142aad44572ba8545b4698b27c56&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"阿里马涛:重新定义云时代的开源操作系统 | 人物志","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzUxNjE3MTcwMg==&mid=2247484788&idx=1&sn=3603207b75136ab43bbd4f983edfa52d&chksm=f9aa37adceddbebbc426c803805f11238ffc176b50257a0d4bb90e455e2691fd47a35d968a37&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"阿里云官方推出操作系统“等保合规”镜像 -- Alibaba Cloud Linux 等保2.0三级版","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzUxNjE3MTcwMg==&mid=2247484689&idx=1&sn=c9590700df6bb981528f4b1a9eaf81b3&chksm=f9aa37c8ceddbedebf4fcb5e139ef0b00da5c54db00e0f14ebec9009387e8afda761d6cfe5ee&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"面对疾风吧!io_uring 优化 nginx 实战演练","attrs":{}}]}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b8/b892c228205f030b8f36c7bb15107a3f.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章