多处理器使用松散的内存模型可能会非常混乱,写操作可能会无序,读操作可能会返回不是我们想要的值,为了解决这些问题,我们需要使用内存栅栏(memory fences),或者说内存屏障(memory barrier)。

  X86平台可能还算是使用松散内存模型的多处理器中还算比较好的了,它针对内存顺序有一定的规范要求,原文如下:

Loads are not reordered with other loads.
Stores are not reordered with other stores.
Stores are not reordered with older loads.
In a multiprocessor system, memory ordering obeys causality (memory ordering respects transitive visibility).
In a multiprocessor system, stores to the same location have a total order.
In a multiprocessor system, locked instructions have a total order.
Loads and stores are not reordered with locked instructions.

  其中Load是载入的意思,Store是存储的意思,我们可以简单的对应read和write。这些规则主要的意思就是,Load操作不会和其他Load操作进行操作重排,Store操作不会和其他Store操作进行重排,Store操作不会和之前的Load操作进行重排,使用锁指令的时候不会进行操作重排,其中有些意思我也不是理解的非常透彻,大家自己理解。

  同时X86平台也提供了内存屏障的操作指令,例如mfence,lfence和sfence。但是使用内存屏障指令会导致执行效率的下降,因为CPU需要循环等待。

  这里有个重要的问题,你既不希望编译器针对你的代码加上栅栏导致运行效率过低,同时也不希望它给你生成不正确的代码。

  在X86平台上,有一条重要的事项没有保障,就是“Load操作可能在不同的位置上面比它之前的Store操作更早的执行”,这里的位置是内存位置,比如我们简单的认为是一个变量。

  有一个例子可以说明这个问题,比如x和y是在共享内存中的,他们被初始化为0,r1,r2是处理器的2个寄存器。

Thread 0 Thread 1
mov [_x], 1 mov [_y], 1
mov r1, [_y] mov r2, [_x]

  当Thread0, 和1这两个线程同时在不同的CPU上被执行,就可能会发生r1, r2的值为0的情况。注意发生这种情况的原因就是因为CPU在Store操作之前先执行了Load操作。

  发生这种事情会有什么影响呢?那我们举一个多线程锁的特殊例子,Peterson lock,它是一个针对2个线程互斥的特殊算法。它假设2个线程都有自己的线程ID,不是0,就是1,也就是说其中一个线程ID为0,一个为1.他的实现代码是这样的。

class Peterson
{
private:
// indexed by thread ID, 0 or 1
bool _interested[];
// who's yielding priority?
int _victim;
public:
Peterson()
{
_victim = ;
_interested[] = false;
_interested[] = false;
}
void lock()
{
// threadID is thread local,
// initialized either to zero or one
int me = threadID;
int he = - me;
_interested[me] = true;
_victim = me;
while (_interested[he] && _victim == me)
continue;
}
void unlock()
{
int me = threadID;
_interested[me] = false;
}
}

  为了解释这个算法是如何工作的,让我们假设一下,我们就是其中的一个线程。当我(就是那个线程)需要获得锁的时候,我先将我感兴趣的_interested slot置为true,只有我能写这个slot,其他的线程只能读。同时我将_victim(牺牲者)置为我自己的ID。然后我检查另外的一个线程是否已经获得了锁,如果另外的线程已经获取到了锁,那么我就Spin,也就是循环等待。但是一旦另外的线程释放了锁,并且将它自己设置为_victim,那我就可以获取到锁了,然后就可以针对临界区的数据进行操作了。

  如果已经基本理解了这个算法的意思,我们将问题简化一下,只抽取其中最关键部分的代码,将_interested数组中的2个slot,使用2个变量zeroWants和oneWants来替代。

  他们初始化为:

zeroWants = false;
oneWants = false;
victim = ;

  考虑2个线程同时操作时,我们列出下面的表格:

Thread 0 Thread 1
zeroWants = true;
victim = 0;
while (oneWants && victim == 0)
     continue;
// critical code
zeroWants = false;
oneWants = true;
victim = 1;
while (zeroWants && victim == 1)
      continue;
// critical code
oneWants = false;

  最后,我们再将上面的表格转换成伪汇编代码

Thread 0 Thread 1
store(zeroWants, 1)
store(victim, 0)
r0 = load(oneWants)
r1 = load(victim)
store(oneWants, 1)
store(victim, 1)
r0 = load(zeroWants)
r1 = load(victim)

  先我们来看下Load和Store zeroWants和oneWants2个变量,在上面的表格是我们期待的正确Load和Store顺序。但是处理器是允许先Load oneWants,然后再Store zeroWants的,同样也允许先Load zeroWants,然后再Store oneWants的。那么乱序执行的样子就可能就是下表这个样子:

Thread 0 Thread 1
r0 = load(oneWants)
store(zeroWants, 1)
store(victim, 0)
r1 = load(victim)
r0 = load(zeroWants)
store(oneWants, 1)
store(victim, 1)
r1 = load(victim)

  如果这样执行的话,由于zeroWants和oneWants初始化为0,那么r0, r1也就会变成0,那么spin loop(循环等待)就永远不会被执行,2个线程同时可以针对临界区的数据进行操作! Peterson lock 在X86平台上就失效了!

  当然是有办法解决这个问题的,那么就是使用fence类的指令来强制按照正确的顺序执行。在同一个线程中,fence指令可以加在任何store zeroWants和load oneWants之间,在另外一个线程中情况也是一样。这样就能保证正确的执行顺序了。

  当然我们在平时的编程过程中,并不需要过多的考虑乱序执行以及内存屏障,我们的操作系统比如linux,在它实现加锁的时候,已经将内存屏障的指令加入进去了。

X86平台乱序执行简要分析(翻译为主)的更多相关文章

  1. 【UEFI】---史上最全的X86平台启动流程分析(软硬结合)

    最近研究了下X86处理器的启动流程分析,相比于常见的ARM来说,X86平台启动流程真的复杂了很多,本次基于项目实际的两个问题入手,研究了包括以下几个部分的内容: 1. 从硬件角度看启动流程 2. 从软 ...

  2. 简要分析武汉一起好P2P平台的核心功能

    写作背景 加入武汉一起好,正式工作40天了,对公司的核心业务有了更多的了解,想梳理下自己对于P2P平台的认识. 武汉一起好,自己运营的yiqihao.com,是用PHP实现的,同时也帮助若干P2P平台 ...

  3. 英语学习/词典app行业top5简要分析

    综述 根据豌豆夹上的下载量如下图所示,我们组确定的国内行业top5分别是:有道词典(黄明帅负责),金山词霸(黄珂锐负责),百度翻译(刘馨负责),百词斩(贾猛负责),英语流利说(亢建强负责)(时间有限, ...

  4. ITS简要分析流程(using Qiime)

    Qiime安装 参考资料:http://blog.sina.com.cn/s/blog_83f77c940101h2rp.html Qiime script官方说明http://qiime.org/s ...

  5. Android Hal层简要分析

    Android Hal层简要分析 Android Hal层(即 Hardware Abstraction Layer)是Google开发的Android系统里上层应用对底层硬件操作屏蔽的一个软件层次, ...

  6. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  7. 面向英特尔® x86 平台的 Unity* 优化指南: 第 1 部分

    原文地址 目录 工具 Unity 分析器 GPA 系统分析器 GPA 帧分析器 如要充分发挥 x86 平台的作用,您可以在项目中进行多种性能优化,以最大限度地提升性能. 在本指南中,我们将展示 Uni ...

  8. hidraw设备简要分析

    关键词:hid.hidraw.usbhid.hidp等等. 下面首先介绍hidraw设备主要用途,然后简要分析hidraw设备驱动(但是不涉及到相关USB/Bluwtooth驱动),最后分析用户空间接 ...

  9. 基于X86平台的PC机通过网络发送一个int(32位)整数的字节顺序

    1.字节顺序 字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端.大端两种字节顺序.小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处:大端字节序是高字节数据存 ...

随机推荐

  1. 一个Java递归程序

    先来没事搜了一些面试题来做,其中一道:输入一个整数,求这个整数中每位数字相加的和? 思考:1.如何或得每一位数:假如是1234,   1234%10=4,得到个位:(1234/10)%10=3得到十位 ...

  2. Oracle练习题(1~19)

    1. 查询Student表中的所有记录的Sname.Ssex和Class列. 2. 查询教师所有的单位即不重复的Depart列. 3. 查询Student表的所有记录. 4. 查询Score表中成绩在 ...

  3. Android Webview 背景透明

    两个关键点: 1         fBarParams.format = PixelFormat.RGBA_8888; 2 mWebView.setBackgroundColor(Color.TRAN ...

  4. DataGridView复选框实现全选功能,并取被选中的某行某列的值(三)

    目标: 一.选中全选这个复选框,会选中第一列所有的复选框 拉过来一个CheckBox控件(CheckBox1)覆盖在第一列的标题上,文本值:全选 方法:双击上面拉的CheckBox控件,进入其事件 p ...

  5. CodeIgniter 引入自定义公共函数

    CodeIgniter 中公共函数不能追加,可以通过 helper 辅助函数实现. 创建 common_helper.php 文件,定义所需公共函数,存放至 application/helpers 目 ...

  6. Validform自定义提示效果-使用自定义弹出框

    $(function(){ $.Tipmsg.r=null; $("#add").Validform({ tiptype:function(msg){ layer.msg(msg) ...

  7. 0xC0000005: 读取位置 0x00000000 时发生访问冲突

    遇见这种问题一般都是空指针,即:指针里没有赋值~ 如果你对null 进行操作就会产生空指针异常 Object obj = new Object(); 你要知道 obj是一个Object指针变量,指向O ...

  8. NOP初学记录

    1.  介绍的话不多说了.直接先来简单的安装跟配置先以3.6版本为例: 附带官网地址: http://www.nopcommerce.com   自行下载. 中文网:http://www.nopchi ...

  9. SQL with PL/SQL

    DDL commands --> create user / table / view / sequence alter DML --> data manipulation languag ...

  10. SharePoint 沙盒解决方案 VS 场解决方案

    博客地址 http://blog.csdn.net/foxdave 最近看书正好看到了关于沙盒解决方案的介绍,便整理记录一下. 虽然沙盒解决方案已经在最新的SharePoint开发中被否决弃用了(被A ...