WiredTiger是一种高性能的开源存储引擎,现已在MongoDB中作为内模式应用。WiredTiger支持行存储、列存储两种存储模式,采用LSM Tree方式进行索引记录
WiredTiger支持事务的ACID特性(原子性、一致性、隔离性、持久性)。对数据的存储方式可采用简易的key/value形式进行存储,也可以使用包含索引映射的数据模式层的方式进行存储。

WiredTiger存储引擎可应用于现代多核CPU架构之上。采用多种实现方式,如风险指针(hazard pointers)、无锁算法(lock-free algorithms)、快锁(fast latching)、消息传递(message passing)等方法。

风险指针(Hazard Pointers)

风险指针是一种内存管理的方法,允许内存被回收后任意重用。该内存管理方法是无等待的(wait-free)。核心操作仅需要单字的内存读取和写入访问。该方法同样提供了ABA问题的一个无锁解决方案。对于指针的管理,通常关注与如何回收已经删除掉的对象所占用的内存。在基于锁的对象的情形下,当线程从对象中删除一个节点时,在它被重用或者重新分配之前,很容易保证没有其他线程会在此后访问该节点的内存。但在无锁动态对象就出现了问题。为了保证无锁的对象的顺利执行,每个线程必须要有无限制的机会在任意时间操作该对象。

内存回收问题是如何允许被删除节点的内存被释放,同时还保证没有线程访问被释放的内存,以及如何以一种无锁的方式做到这一点。之前的用以动态无锁对象的节点重用方法主要有三种:

  1. IBM标签方法(更新计数)
  2. 无锁引用计数
  3. 基于总体引用计数或者每个线程的时间戳方法

风险指针,是一种以无锁对象的内存回收方法。它对每个被删除的节点的分摊时间是预期的常量。无论线程是失败还是延迟,它都能对不可重用的被删除节点的总数保持一个上界。其核心思想是:将一些被称为风险指针(hazard pointer)的单写者多读者的共享指针,与每个想要访问无锁对象的线程关联起来。一个风险指针要么是NULL,要么指向一个节点,该节点之后可能会被该线程访问,而不需要验证对该节点的这个引用是否有效。每个风险指针只能被其拥有者线程写入,但是可以被别的线程读取。

基础模型

基本计算模型是异步共享内存模型。在该模型中一组线程通过在一组共享内存位置上的内存访问操作原语来沟通。线程运行在任意速度,可以有任意延迟。线程对任何其他线程的速度或状态不做任何假设。

共享对象占有一组共享内存位置。对象是一个抽象的对象类型实现的实例,它定义了在对象上可以允许的操作的语义。

原子原语

除了读取和写入外,共享内存位置上的基本操作还包括CAS(compare-and-swap)和load-linked/store-conditional(LL/SC)。

CAS有三个参数:内存位置的地址、预期值、新值。当且仅当存储单元存储的是预期值的时候,新值才能原子地写入该存储单元。该操作返回一个布尔值,用以表示是否发生了写操作。

LL需要一个参数:内存位置的地址,该操作将返回其内容。SS有两个参数:一个内存位置的地址和一个新值。只有当前线程最后使用LL读取该位置之后,没有其他线程写入该内存位置,新值才会原子地写入到该内存位置的地址,并返回一个布尔值,指示自从当前线程最后使用LL读取该位置之后是否有任何其他线程写入过该内存位置。

ABA问题

问题描述如下:它发生在当一个线程从共享位置读取值A,然后其他线程改变该位置为不同的值,例如B。当该线程再次将A放回该共享位置处时,新的线程再次从该位置读取该共享位置处的值时,会误认为该值没有改变,从而错误的执行。

Hazard Pointer要解决的核心问题是如何安全释放内存,该问题解决在实现无锁算法时有两个关键的影响:

  1. 保证了关键节点的访问是合法的,不会导致程序尝试去读取已经释放了的内存。
  2. 保证了ABA问题不会出现,程序逻辑正确的前提。

无锁算法中释放内存的难点在于当线程释放了一块内存后,是无法获知是否有别的线程也同时持有该块内存的指针并需要访问。

解决方案

  1. 建立一个全局数组,数组容量为线程数目,每个线程只能修改自己的数组元素,而不允许修改其他的数组元素,但可以读别的数组元素。
  2. 当线程尝试访问一个关键数据节点时,先把该节点指针赋给自己的数组元素(即不要释放这个节点)。
  3. 每个线程自己维护一个私有链表,当线程准备释放掉某个节点时,将该节点放入到链表中。当链表内的数目达到一个设定的数目后,遍历该链表用于释放链表内所有节点。
  4. 当释放节点时,需要检查全局数组,确定没有任何一个线程的数组元素与当前指针相同时,就释放该节点。否则仍然滞留在自己的链表中。

Hazard Pointer主要应用在实现无锁队列上。

  1. 队列上的元素任何时候,只可能被其中一个线程成功地从队列上取下来,因此每个线程的链表中的元素肯定是唯一的。
  2. 线程在操作无锁队列时,任何时候基本只需要处理一个节点,如果有特殊需求,就需要有额外的扩展
  3. 对于某个节点,多线程同时持有该节点的指针的现象在时间上是非常短暂的。只有当这几个线程同时尝试取下该节点,它们才可能同时持有该节点的指针,一旦某线程成功将节点取下,其它线程很快就会发现,并尝试继续操作下一个节点。

风险指针(Hazard Pointer) 内存空间共享模型的更多相关文章

  1. 在64位系统下,指向int型的指针占的内存空间多大?

    不废话,请看代码演示如下: 注意使用的操作系统的位数,不同位数的操作系统,结果不一样! 我是用的是64位的操作系统! linux下示例代码如下: #include <stdio.h> in ...

  2. C语言指针及占据内存空间

    第一.了解内存空间 本文章文字有点多,会有点枯燥,配合图文一起看可以缓解枯燥,耐心阅读哦!!! 先了解内存地址,才更好的理解指针! 我们可以把内存想象为成一列很长很长的货运火车,有很多大小相同的车厢, ...

  3. C语言中指针占据内存空间问题

    以前一直有个疑问,指向不同类型的指针到底占用的内存空间是多大呢? 这个问题我多次问过老师,老师的答案是"指向不同类型的指针占据的内存空间大小不同",我一直很之一这个答案,今天我就做 ...

  4. 《C和指针(Pointer on c)》 学习笔记(转自:http://dsqiu.iteye.com/blog/1687944)

    首先本文是对参考中三个连接的博客进行的整理,非常感谢三位博主的努力,每次都感叹网友的力量实在太强大了…… 第一章 快速上手 1.  在C语言中用/*和*/来注释掉这段代码,这个实际上并不是十分的安全, ...

  5. 《C和指针(Pointer on c)》 学习笔记

    转载:http://dsqiu.iteye.com/blog/1687944 首先本文是对参考中三个连接的博客进行的整理,非常感谢三位博主的努力,每次都感叹网友的力量实在太强大了…… 第一章 快速上手 ...

  6. C++二级指针第二种内存模型(二维数组)

    C++二级指针第二种内存模型(二维数组) 二维数组 二维数组本质上是以数组作为数组元素的数组,即“数组的数组”. 定义 类型说明符 数组名[常量表达式][常量表达式] 例如: float a[3][4 ...

  7. lock free数据结构内存回收技术-hazard pointer

    lock free数据结构一般来说拥有比基于lock实现的数据结构更高的性能,但是其实现比基于lock的实现更为复杂,需要处理的难题包括预防ABA问题,内存如何重用和回收等.通常,最简单最有效的处理A ...

  8. C++二级指针第一种内存模型(指针数组)

    二级指针第一种内存模型(指针数组) 指针的输入特性:在主调函数里面分配内存,在被调用函数里面使用指针的输出特性:在被调用函数里面分配内存,主要是把运算结果甩出来 指针数组 在C语言和C++语言中,数组 ...

  9. C/C++ 错误笔记-在给结构体中的指针赋值时,要注意该指针是否已指向内存空间

    先来看下面的例子: #include <stdlib.h> #include <string.h> #include <stdio.h> #pragma warni ...

随机推荐

  1. JVM 运行时数据区详解

    一.运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同数据区域. 1.有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,所有的线程共享这些数据区. 2.第二种则 ...

  2. 《松本行弘的程序世界》读书笔记(上)——面向对象、程序块、设计模式、ajax

    1. 前言 半个月之前买了这本书,还是经园子里的一位网友推荐的.到现在看了一半多,基础的都看完了,剩下的几章可做高级部分来看.这本书看到现在,可以说感触很深,必须做一次读书笔记! 关于这本书,不了解的 ...

  3. Tensorflow运用RNN注意事项

    一.学习单步的RNN:RNNCell 如果要学习TensorFlow中的RNN,第一站应该就是去了解“RNNCell”,它是TensorFlow中实现RNN的基本单元,每个RNNCell都有一个cal ...

  4. 修改wireshark协议解析规则

    不同的协议有不同的解码器,wireshark尝试为每个包尝试找到正确的解码器,特定的情况有可能会选择错误的解码器. 1.使用了其它协议的标准端口,被错误解码,使用udp的80端口发送数据被当作QUIC ...

  5. Entity Framework 6.x - Code First 默认创建数据库的位置

    在集成DbContext的派生类中的构造函数里,如果没有指定配置文件中的数据库连接字符串的name,默认就是: Data Source=(localdb)\MSSQLLocalDB;Initial C ...

  6. 几种常见算法的Python实现

    1.选择排序 选择排序是一种简单直观的排序算法.它的原理是这样:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的后 ...

  7. UIKit 框架之UIView一

    - (id)initWithFrame:(CGRect)aRect //通过一个矩形对象初始化 Configuring a View’s Visual Appearance //配置视觉展示 @pro ...

  8. 分布式理论(六)—— Raft 算法

    前言 我们之前讲述了 Paxos 一致性算法,虽然楼主尝试用最简单的算法来阐述,但仍然还是有点绕.楼主最初怀疑自己太笨,后来才直到,该算法的晦涩难懂不是只有我一个人这么认为,而是国际公认! 所以 Pa ...

  9. Dockerfile构建镜像

    Dockerfile构建镜像的步骤: 从基础镜像运行一个容器 执行一条指令,对容器做出修改 执行类似docker commit的操作,提交一个新的镜像层 再基于刚提交的镜像运行一个新的容器 执行Doc ...

  10. 集合框架(TreeSet原理)

    特点: TreeSet是用来排序的,可以指定一个顺序,对象存入之后会按照指定的顺序排列 使用方式: 自然排序(Comparable) TreeSet类的add()方法中会把存入的对象提升为Compar ...