使用MemoryBarrier,Volatile进行同步

上一节介绍了使用信号量进行同步,本节主要介绍一些非阻塞同步的方法。本节主要介绍MemoryBarrier,volatile,Interlocked。

MemoryBarriers

本文简单的介绍一下这两个概念,假设下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
class Foo
{
    int _answer;
    bool _complete;
 
    void A()
    {
        _answer = 123;
        _complete = true;
    }
 
    void B()
    {
        if (_complete) Console.WriteLine(_answer);
    }
}

如果方法A和方法B同时在两个不同线程中运行,控制台可能输出0吗?答案是可能的,有以下两个原因:

  • 编译器,CLR或者CPU可能会更改指令的顺序来提高性能
  • 编译器,CLR或者CPU可能会通过缓存来优化变量,这种情况下对其他线程是不可见的。

最简单的方式就是通过MemoryBarrier来保护变量,来防止任何形式的更改指令顺序或者缓存。调用Thread.MemoryBarrier会生成一个内存栅栏,我们可以通过以下的方式解决上面的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
using System.Threading;
class Foo
{
    int _answer;
    bool _complete;
 
    void A()
    {
        _answer = 123;
        Thread.MemoryBarrier();    // Barrier 1
        _complete = true;
        Thread.MemoryBarrier();    // Barrier 2
    }
 
    void B()
    {
        Thread.MemoryBarrier();    // Barrier 3
        if (_complete)
        {
            Thread.MemoryBarrier();       // Barrier 4
            Console.WriteLine(_answer);
        }
    }
}

上面的例子中,barrier1和barrier3用来保证指令顺序不会改变,barrier2和barrier4用来保证值变化不被缓存。一个好的处理方案就是我们在需要保护的变量前后分别加上MemoryBarrier。

在c#中,下面的操作都会生成MemoryBarrier:

  • Lock语句(Monitor.Enter,Monitor.Exit)
  • 所有Interlocked类的方法
  • 线程池的回调方法
  • Set或者Wait信号
  • 所有依赖于信号灯实现的方法,如starting或waiting 一个Task

因为上面这些行为,这段代码实际上是线程安全的:

1
2
3
4
int x = 0;
Task t = Task.Factory.StartNew(() => x++);
t.Wait();
Console.WriteLine(x);    // 1

在你自己的程序中,你可能重现不出来上面例子所说的情况。事实上,从msdn上对MomoryBarrier的解释来看,只有对顺序保护比较弱的多核系统才需要用到MomoryBarrier。但是有一点需要注意:多线程去修改变量并且不使用任何形似的锁或者内存栅栏是会带来一定的麻烦的。

下面一个例子能够很好的说明上面的观点(在你的VisualStudio中,选择Release模式,并且Start Without Debugging重现这个问题):

1
2
3
4
5
6
7
8
9
10
bool complete = false;
var t = new Thread(() =>
{
    bool toggle = false;
    while (!complete) toggle = !toggle;
});
t.Start();
Thread.Sleep(1000);
complete = true;
t.Join();        // Blocks indefinitely

这个程序永远不会结束,因为complete变量被缓存在了CPU寄存器中。在while循环中加入Thread.MemoryBarrier可以解决这个问题。

volatile关键字

另外一种更高级的方式来解决上面的问题,那就是考虑使用volatile关键字。Volatile关键字告诉编译器在每一次读操作时生成一个fence,来实现保护保护变量的目的。具体说明可以参见msdn的介绍

VolatileRead和VolatileWrite

Volatile关键字只能加到类变量中。本地变量不能被声明成volatile。这种情况你可以考虑使用System.Threading.Volatile.Read方法。我们看一下System.Threading.Volatile源码如何实现这两个方法的:

1
2
3
4
5
6
7
8
9
10
11
public static bool Read(ref bool location)
{
    bool flag = location;
    Thread.MemoryBarrier();
    return flag;
}
public static void Write(ref bool location, bool value)
{
    Thread.MemoryBarrier();
    location = value;
}

  

一目了然,通过MemoryBarrier来实现的,但是他只在读操作的后面和写操作的前面加了MemoryBarrier,那么你应该考虑,如果你先使用Volatile.Write再使用Volatile.Read是不是可能有问题呢?

c#中ConcurrentDictionary中使用了Volatile类来保护变量,有兴趣的读者可以看看c#的开发者是如何使用这个方法来保护变量的。

Interlocked

使用MemoryBarrier并不总是一个好的解决方案,尤其在不需要锁的情况下。Interlocked方法提供了一些常用的原子操作来避免前面文章提到的一系列的问题。如使用Interlocked.Increment来替代++,Interlocked.Decrement来替代--。Msdn的文档中详细的介绍了相关的用法和原理。C#中的源码里也经常能看见Interlocked相关的使用。

本文介绍了一些除了锁和信号量之外的一些同步方式,欢迎批评与指正。

 
分类: .Net

MemoryBarrier,Volatile的更多相关文章

  1. 细说.NET中的多线程 (六 使用MemoryBarrier,Volatile进行同步)

    上一节介绍了使用信号量进行同步,本节主要介绍一些非阻塞同步的方法.本节主要介绍MemoryBarrier,volatile,Interlocked. MemoryBarriers 本文简单的介绍一下这 ...

  2. Java 开发, volatile 你必须了解一下

    上一篇文章说了 CAS 原理,其中说到了 Atomic* 类,他们实现原子操作的机制就依靠了 volatile 的内存可见性特性.如果还不了解 CAS 和 Atomic*,建议看一下我们说的 CAS ...

  3. DSP 中关键字extern,cregister,Near ,Far,restrict,volatile

    extern:extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.另外,extern也可用来进行链接指定. const: 可以 ...

  4. jvm高级特性(5)(1)(原子性,可见性,有序性,volatile,概述)

    JVM高级特性与实践(十二):高效并发时的内外存交互.三大特征(原子.可见.有序性) 与 volatile型变量特殊规则 简介: 阿姆达尔定律(Amdahl):该定律通过系统中并行化与串行化的比重来描 ...

  5. volatile、Synchronized实现变量可见性的原理,volatile使用注意事项

    变量不可见的两个原因 Java每个线程工作都有一个工作空间,需要的变量都是从主存中加载进来的.Java内存模型如下(JMM): 线程访问一个共享的变量时,都需要先从主存中加载一个副本到自己的工作内存中 ...

  6. java内存模型(线程,volatile关键字和sychronized关键字)

    volatile关键字 用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况. ...

  7. 多线程的指令重排问题:as-if-serial语义,happens-before语义;volatile关键字,volatile和synchronized的区别

    一.指令重排问题 你写的代码有可能,根本没有按照你期望的顺序执行,因为编译器和 CPU 会尝试指令重排来让代码运行更高效,这就是指令重排. 1.1 虚拟机层面 我们都知道CPU执行指令的时候,访问内存 ...

  8. Volatile关键字&&DCL单例模式,volatile 和 synchronized 的区别

    Volatile 英文翻译:易变的.可变的.不稳定的. 一.volatile 定义及用法 多个线程的工作内存彼此独立,互不可见,线程启动的时候,虚拟机为每个内存分配一块工作内存,不仅包含了线程内部定义 ...

  9. 基础篇:详解锁原理,volatile+cas、synchronized的底层实现

    目录 1 锁的分类 2 synchronized底层原理 3 Object的wait和notify方法原理 4 jvm对synchronized的优化 5 CAS的底层原理 6 CAS同步操作的问题 ...

随机推荐

  1. php将中文插入数据库出现乱码

    通过php向mysql数据库插入数据,然后在数据库中查看的时候全是乱码(中文),但是取出之后放在页面上仍然正常.就是通过数据库查看的时候全是乱码不能阅读. mysql以UTF-8编码来保存中文,页面提 ...

  2. SWT中Display和Shell是个什么东东

    Display:与操作系统沟通的桥梁 我们在前面说过,每个swt程序在最开始都必须创建一个Display对象.Display对象起什么作用呢?它是swt与操作系统沟通的一座桥梁.它负责swt和操作系统 ...

  3. css中padding中样式的顺序含义

    4种可能的情况.举例说明: padding:10px; 四个内边距都是10px padding:5px 10px; 上下5px 左右10px padding:5px 10px 15px; 上5px 右 ...

  4. 寻找失踪的整数数组(Find the missing integer)

    排列a包含N分子,其元素属于[0,N]之间,且不存在反复的元素.请你找出数组中缺失的元素(由于[0,N]之间有N+1个元素.而数组仅仅能存储N个元素.所以必定缺少一个元素).当中对数组的操作满足下列的 ...

  5. IOS开发笔记 - 基于wsdl2objc调用webservice

    为了方便在ios下调用webserivce,找来了wsdl2objc这样一个开源的框架来解析webservice方便在ios下引用. 下面做个小例子. 1.首先是用Asp.net搭建一个测试的webs ...

  6. ecshop中getAll ,getOne ,getRow的区别

    ecshop的数据库抽象层其实就是在模仿adodb $GLOBALS['db']->getAll($sql);//以二维关联数组返回所有数据 $GLOBALS['db']->getOne( ...

  7. 《深入理解mybatis原理》 Mybatis初始化机制具体解释

    对于不论什么框架而言.在使用前都要进行一系列的初始化,MyBatis也不例外. 本章将通过下面几点具体介绍MyBatis的初始化过程. 1.MyBatis的初始化做了什么 2. MyBatis基于XM ...

  8. cocos2d 创建一个黑白纹理

    @interface myGrayTexture : CCTexture2D // @param exposure 曝光 +(id) textureWithFile:(NSString*) file ...

  9. Custom Media Player in WPF (Part 1)

    First of all I would like to welcome everyone to my new blog and wish you all a happy new year… Thro ...

  10. 'Basic' attribute type should not be a persistence entity/a container

    正在使用IDEA进行HIbernate开发时间,从datasource由此产生的实体映射不理想.需要手动更改. 投身于实体类的属性Setter时间.临时有红tip:'Basic' attribute ...