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

【转】细说.NET中的多线程 (六 使用MemoryBarrier,Volatile进行同步)上一节介绍了使用信号量进行同步,本节主要介绍一些非阻塞同步的方法。本节主要介绍MemoryBarrier,volatile,Interlocked。MemoryBarriers本文简单的介绍一下这两个概念,假设下面的代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 usingSystem; .

大家好,又见面了,我是你们的朋友全栈君。

上一节介绍了使用信号量进行同步,本节主要介绍一些非阻塞同步的方法。本节主要介绍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相关的使用。

 

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/158111.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • mysql和oracle的sql区别有什么_orical与mysql

    mysql和oracle的sql区别有什么_orical与mysqlMysql与Oracle区别1.Oracle是大型数据库而Mysql是中小型数据库,Oracle市场占有率达40%,Mysql只有20%左右,同时Mysql是开源的而Oracle价格非常高。2.Oracle支持大并发,大访问量,是OLTP最好的工具。3.安装所用的空间差别也是很大的,Mysql安装完后才152M而Oracle有3G左右,且使用的时候Oracle占用特别大的内存空间和其他机器性…

    2022年9月14日
    2
  • H3C交换机配置命令大全【转载】[通俗易懂]

    H3C交换机配置命令大全【转载】[通俗易懂]杭州华三通信技术有限公司(简称H3C),致力于IP技术与产品的研究、开发、生产、销售及服务。H3C不但拥有全线路由器和以太网交换机产品,还在网络安全、IP存储、IP监控、语音视讯、WLAN、SOHO及软件管理系统等领域稳健成长。在以太网领域,H3C经历多年的耕耘和发展,积累了大量业界领先的知识产权和专利,可提供业界从核心到接入10多个系列上百款交换机产品。所有产品全部运行H3C自主知识产权的…

    2022年6月20日
    32
  • string转JSONObject遍历多层找到key的value

    string转JSONObject遍历多层找到key的value先上代码packagecom.zhph;/***@Description:*@Author:xuhaibo*@Date:${Date}*@ModifiedBy:*/importnet.sf.json.JSONArray;importnet.sf.json.JSONObject;importjava.util.Iterator;/****@compa

    2022年8月23日
    9
  • 编译器警告RegisterStartupScript已经过时

    编译器警告RegisterStartupScript已经过时protectedvoidAlertMsg(stringmsg){this.Page.RegisterStartupScript(“alert”,”<scriptlanguage=\”javascript\”>alert(‘”+msg+”‘);</script>”);}使用上面的代码建立的客户端脚本块编译器会警告Reg…

    2022年7月20日
    12
  • 一致性哈希算法的原理与实现

    一致性哈希算法的原理与实现分布式系统中对象与节点的映射关系,传统方案是使用对象的哈希值,对节点个数取模,再映射到相应编号的节点,这种方案在节点个数变动时,绝大多数对象的映射关系会失效而需要迁移;而一致性哈希算法中,当节点个数变动时,映射关系失效的对象非常少,迁移成本也非常小。本文总结了一致性哈希的算法原理和Java实现,并列举了其应用。作者:王克锋出处:https://kefeng.wang/2018/08/1…

    2022年7月27日
    4
  • 物联网架构方案思考「建议收藏」

    物联网架构方案思考「建议收藏」1.前言1.1.系统设计有无通用方案?一般的IT系统,稍微复杂一些,都会存在一个架构。架构在初期可能不觉得有多么重要,但随着业务发展,架构可能成为系统开发的瓶颈,导致无法再迭代下去。不同的系统,会有不同的架构,即使同一个系统,由不同的架构师设计也会有不同的架构。架构不存在正确与否的,只能说在不同的场景,存在优劣之分。如何设计一个系统,此问…

    2022年9月18日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号