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

MemoryBarriers

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

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会生成一个内存栅栏,我们可以通过以下的方式解决上面的问题:

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 3if (_complete){Thread.MemoryBarrier();       // Barrier 4Console.WriteLine(_answer);}}
}

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

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

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

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

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

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

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

        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源码如何实现这两个方法的:

    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://www.cnblogs.com/myprogram/p/4940219.html

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

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

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

  2. 【转】细说.NET中的多线程 (二 线程池)

    上一章我们了解到,由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源.最好的办法是使用线程池,线程池能够避免当前进程中大量的线程导致操作系统不停的进行线程切换,当线程数 ...

  3. 【转】细说.NET 中的多线程 (一 概念)

    为什么使用多线程 1.使用户界面能够及时响应用户的输入 当某个应用程序在进行大量运算时候,为了保证应用程序能够随时响应客户的输入,这个时候我们往往需要让大量运算和响应用户输入这两个行为在不同的线程中进 ...

  4. 【转】细说.NET中的多线程 (五 使用信号量进行同步)

    上一节主要介绍了使用锁进行同步,本节主要介绍使用信号量进行同步 使用EventWaitHandle信号量进行同步 EventWaitHandle主要用于实现信号灯机制.信号灯主要用于通知等待的线程.主 ...

  5. 【转】细说.NET中的多线程 (四 使用锁进行同步)

    通过锁来实现同步 排它锁主要用来保证,在一段时间内,只有一个线程可以访问某一段代码.两种主要类型的排它锁是lock和Mutex.Lock和Mutex相比构造起来更方便,运行的也更快.但是Mutex可以 ...

  6. 【转】细说.NET中的多线程 (三 使用Task)

    上一节我们介绍了线程池相关的概念以及用法.我们可以发现ThreadPool. QueueUserWorkItem是一种起了线程之后就不管了的做法.但是实际应用过程,我们往往会有更多的需求,比如如何更简 ...

  7. 细说.NET中的多线程 (四 使用锁进行同步)

    通过锁来实现同步 排它锁主要用来保证,在一段时间内,只有一个线程可以访问某一段代码.两种主要类型的排它锁是lock和Mutex.Lock和Mutex相比构造起来更方便,运行的也更快.但是Mutex可以 ...

  8. 如何避免操作系统中多线程资源竞争的互斥与同步?

    作者 | 小林coding 来源 | 小林coding(ID:CodingLin) 前言 先来看看虚构的小故事 已经晚上 11 点了,程序员小明的双手还在键盘上飞舞着,眼神依然注视着的电脑屏幕. 没办 ...

  9. C#中的多线程 - 并行编程 z

    原文:http://www.albahari.com/threading/part5.aspx 专题:C#中的多线程 1并行编程Permalink 在这一部分,我们讨论 Framework 4.0 加 ...

  10. C# 多线程六之Task(任务)三之任务工厂

    1.知识回顾,简要概述 前面两篇关于Task的随笔,C# 多线程五之Task(任务)一 和 C# 多线程六之Task(任务)二,介绍了关于Task的一些基本的用法,以及一些使用的要点,如果都看懂了,本 ...

最新文章

  1. C#基础总结之四List-Hashtable-冒泡排序
  2. python基础单词-学习Python必背的初级单词有哪些?
  3. 如何学好C、C++------思维方式的转变
  4. 销售Invoice管理流程
  5. HTML之Position用法
  6. javaSE各阶段练习题--面向对象-多态-抽象类-接口
  7. 用 Crontab 进行简单的备份日志任务计划
  8. 智能SQL优化工具--SQL Optimizer for SQL Server(帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 )...
  9. flask gunicorn gevent部署
  10. Linux运维基础知识——开发人员可以掌握的常识
  11. 输入一个数求其倒数c语言,C语言答案(1)
  12. Linux屏幕取词翻译 - 思路及实现过程
  13. android车载蓝牙开发,车载蓝牙开发二
  14. matlab编辑器背景颜色,matlab编辑器颜色风格设置
  15. 星际迷航传奇Star Trek for mac(太空冒险游戏)
  16. kali 控制安卓手机
  17. python中dic.get用法
  18. status_breakpoint谷歌浏览器如何解决?
  19. 判断和推论_数据科学的假设和推论
  20. python写linux命令_Python写的系统常用命令

热门文章

  1. Vue 3为什么要用 Proxy API 替代 DefineProperty API?
  2. arduino两轮平衡车(二)-- 原理讲解
  3. 史上最全的Android基础教程+入门实战训练+处理技巧(建议收藏)|寻找C站宝藏
  4. 基于单片机的功放protues_基于单片机的功放毕业设计
  5. 语音信号处理的一些基础知识
  6. 用“文通慧视”识别屏幕或图片中的文字
  7. 基于Struts2的网上书城(仿当当网)系统设计与实现
  8. TFN TK200光缆普查仪
  9. AutoJs学习-图灵QQ自动聊天机器人
  10. 面向大众征集 “故宫·金榜题名”文创众筹大赛启动