C#SpinWait和volatile一点温习

今天看ConcurrentQueue<T> 源码发现里面居然没有用到lock,我记得ConcurrentDictionary里面是有lock的,lock的是字典里面每一个key,但是ConcurrentQueue<T> 的线程安全确是用SpinWait对象和volatile关键字来实现,于是乎就温习了一下,直接上code

 class Test
    {
        /*
        volatile多用于多线程的环境,当一个变量定义为volatile时,读取这个变量的值时候每次都是从momery里面读取而不是从cache读。
        这样做是为了保证读取该变量的信息都是最新的,而无论其他线程如何更新这个变量。

        volatile 修饰符通常用于由多个线程访问但不使用 lock 语句对访问进行序列化的字段。
        volatile 关键字可应用于以下类型的字段:
        引用类型。
        指针类型(在不安全的上下文中)。 请注意,虽然指针本身可以是可变的,但是它指向的对象不能是可变的。 换句话说,您无法声明“指向可变对象的指针”。
        类型,如 sbyte、byte、short、ushort、int、uint、char、float 和 bool。
        具有以下基类型之一的枚举类型:byte、sbyte、short、ushort、int 或 uint。
        已知为引用类型的泛型类型参数。
        IntPtr 和 UIntPtr。
        可变关键字仅可应用于类或结构字段。 不能将局部变量声明为 volatile。
        */
        static volatile bool _isCompleted = false;
        static void UserModeWait()
        {
            while (!_isCompleted)
            {
                Console.Write("②.");
            }
            Console.WriteLine();
            Console.WriteLine("③等待完成");
        }

        static void HybridSpinWait()
        {   /*
            自旋等待
            一个轻量同步类型(结构体),提供对基于自旋的等待的支持。SpinWait只有在多核处理器下才具有使用意义。在单处理器下,自旋转会占据CPU时间,却做不了任何事。
            SpinWait并没有设计为让多个任务或线程并发使用。因此,如果多个任务或者线程通过SpinWait的方法进行自旋,那么每一个任务或线程都应该使用自己的SpinWait实例。
            */
            var w = new SpinWait();
            while (!_isCompleted)
            {
                // 执行单一自旋。
                w.SpinOnce();
                /*
                 判断对SpinWait.SpinOnce() 的下一次调用是否触发上下文切换和内核转换。
                由NextSpinWillYield属性代码可知,若SpinWait运行在单核计算机上,它总是进行上下文切换(让出处理器)。
                SpinWait不仅仅是一个空循环。它经过了精心实现,可以针对一般情况提供正确的旋转行为以避免内核事件所需的高开销的上下文切换和内核转换;
                在旋转时间足够长的情况下自行启动上下文切换,SpinWait甚至还会在多核计算机上产生线程的时间片(Thread.Yield())以防止等待线程阻塞高优先级的线程或垃圾回收器线程。
                */
                Console.WriteLine("是否触发上下文切换和内核转换: " + w.NextSpinWillYield);
            }
            Console.WriteLine("③等待完成");
        }

        public static void RunTest()
        {
            var t1 = new Thread(UserModeWait);
            var t2 = new Thread(HybridSpinWait);

            Console.WriteLine("①运行用户模式等待");
            t1.Start();

            //将当前执行RunTest()方法线程挂起指定的时间。 让t1线程执行输出②.
            Thread.Sleep(1);
            _isCompleted = true;

            //将当前线程阻塞指定的时间
            Thread.Sleep(TimeSpan.FromSeconds(1));
            _isCompleted = false;

            Console.WriteLine("①运行混合SpinWait构造 等待");
            t2.Start();
            Thread.Sleep(5);
            _isCompleted = true;

            Console.ReadKey();
        }
    }

在网上找了一段java的描述

恐怕比较一下volatile和synchronized的不同是最容易解释清楚的。volatile是变量修饰符,而synchronized则作用于一段代码或方法;看如下三句get代码:

  1. int i1;              int geti1() {return i1;}
  2. volatile int i2;  int geti2() {return i2;}
  3. int i3;              synchronized int geti3() {return i3;}

  geti1()得到存储在当前线程中i1的数值。多个线程有多个i1变量拷贝,而且这些i1之间可以互不相同。换句话说,另一个线程可能已经改 变了它线程内的i1值,而这个值可以和当前线程中的i1值不相同。事实上,Java有个思想叫“主”内存区域,这里存放了变量目前的“准确值”。每个线程 可以有它自己的变量拷贝,而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,线程1里的i1值 是2,线程2里的i1值是3——这在线程1和线程2都改变了它们各自的i1值,而且这个改变还没来得及传递给“主”内存区域或其他线程时就会发生。
   而geti2()得到的是“主”内存区域的i2数值。用volatile修饰后的变量不允许有不同于“主”内存区域的变量拷贝。换句话说,一个变量经 volatile修饰后在所有线程中必须是同步的;任何线程中改变了它的值,所有其他线程立即获取到了相同的值。理所当然的,volatile修饰的变量 存取时比一般变量消耗的资源要多一点,因为线程有它自己的变量拷贝更为高效。
  既然volatile关键字已经实现了线程间数据同步,又要 synchronized干什么呢?呵呵,它们之间有两点不同。首先,synchronized获得并释放监视器——如果两个线程使用了同一个对象锁,监 视器能强制保证代码块同时只被一个线程所执行——这是众所周知的事实。但是,synchronized也同步内存:事实上,synchronized在“ 主”内存区域同步整个线程的内存。因此,执行geti3()方法做了如下几步:
1. 线程请求获得监视this对象的对象锁(假设未被锁,否则线程等待直到锁释放)
2. 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步。。。[后面的不知道怎么表达,汗])
3. 代码块被执行
4. 对于变量的任何改变现在可以安全地写到“主”内存区域中(不过geti3()方法不会改变变量值)
5. 线程释放监视this对象的对象锁
  因此volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。

更通俗的解释:

Volatile 字面的意思时易变的,不稳定的。在C#中也差不多可以这样理解。

编译器在优化代码时,可能会把经常用到的代码存在Cache里面,然后下一次调用就直接读取Cache而不是内存,这样就大大提高了效率。但是问题也随之而来了。

在多线程程序中,如果把一个变量放入Cache后,又有其他线程改变了变量的值,那么本线程是无法知道这个变化的。它可能会直接读Cache里的数据。但是很不幸,Cache里的数据已经过期了,读出来的是不合时宜的脏数据。这时就会出现bug。

用Volatile声明变量可以解决这个问题。用Volatile声明的变量就相当于告诉编译器,我不要把这个变量写Cache,因为这个变量是可能发生改变的。

时间: 2024-08-06 03:41:58

C#SpinWait和volatile一点温习的相关文章

C#并行编程-线程同步原语(Barrier,CountdownEvent,ManualResetEventSlim,SemaphoreSlim,SpinLock,SpinWait,Monitor,volatile)

菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 背景 有时候必须访问变量.实例.方法.属性或者结构体,而这些并没有准备好用于并发访问,或者有时候需要执行部分代码,而这些代码必须单独运行,这是不得不通过将任务分解的方式让它们独立运行. 当任务和线程要访问共享的数据和资源的时候,您必须添加显示的同步,或者使用原子操作或锁. 之前的.NET Framework提供了昂贵的锁机制以及遗留的多线程模型,新的数据结构允许细粒度的并发和并行化,并且降低一定必要的开销,这些数据结

我(webabcd)的文章索引

[最后更新:2014.08.28] 重新想象 Windows Store Apps 系列文章 重新想象 Windows 8 Store Apps 系列文章 重新想象 Windows 8 Store Apps (1) - 控件之文本控件: TextBlock, TextBox, PasswordBox, RichEditBox, RichTextBlock, RichTextBlockOverflow 重新想象 Windows 8 Store Apps (2) - 控件之按钮控件: Button,

[笔记][思维导图]读深入理解JAVA内存模型整理的思维导图

本人记忆差,整理这个思维导图,相当于较认真的看了一遍,整个思维导图,基本上就是对原文的拷贝. 有了层级关系.和本人自己的一些理解.由于思维导图弄出来的图片大于100M了.所以就放出源文件, 更方便的阅读查阅.在csdn太穷了.下载2积分.有需要的希望意思意思.我也要去下载资料呢.(下载地址在最后) 有几点我觉得是看这个本书或则思维导图.你要明白的是: 1. 什么是内存可见性 2. 在java程序中,在底层执行的代码指令并不是完全按照顺序执行的-有重排序的存在 3. volatile 是一个和硬件

关于synchronized与volatile的一点认识

贪婪是一种原罪,不要再追求性能的路上离正确越来越远. 内存模型 java内存模型 重排序 锁synchronized 什么是锁 独占锁 分拆锁 分离锁 分布式锁 volatile 内存模型 java内存模型 提到同步.锁,就必须提到java的内存模型,为了提高程序的执行效率,java也吸收了传统应用程序的多级缓存体系. 在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调.在不同的处理器架构中提供了不同级别的缓存一致性(Cache Coherence),其中一部

volatile的一点理解

对于volatile的理解,我想通过代码来表达. p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco; color: #931a68 } p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco; min-height: 25.0px } p.p

对volatile的一点lijie

理解volatile其实还是有点儿难度的,它与Java的内存模型有关,所以在理解volatile之前需要先了解有关Java内存模型的概念,目前只做初步的介绍. 一.操作系统语义 计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写. 我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存. CPU高速缓存为某个CPU独有,只与在该CPU运行的

就是要你懂Java中volatile关键字实现原理

原文地址http://www.cnblogs.com/xrq730/p/7048693.html,转载请注明出处,谢谢 前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用. 本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,在此之前,有必要讲解一下CPU缓存的相关知识,掌握这部分知识一定会让我们更好地理解volatile的原理,从而更好.更正确地地

Java并发编程:volatile关键字解析(转)

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vola

Java并发编程:volatile关键字解析

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vola