《CLR via C#》读书笔记-线程同步(四)

混合线程同步构造简介

之前有用户模式构造和内核模式构造,前者快速,但耗费CPU;后者可以阻塞线程,但耗时、耗资源。因此.NET会有一些混合了两者的构造,《CLR via C#》的作者给这些构造起了一个别名:混合线程同步构造(Hybrid Thread Synchronization Construct)


混合线程同步构造的例子

混合线程同步构造的例子如下:

  internal sealed class SimpleHybridLock : IDisposable
{
private int m_waiters = ;
private AutoResetEvent m_waiterLock = new AutoResetEvent(false); public void Enter()
{
if (Interlocked.Increment(ref m_waiters) == )
return; m_waiterLock.WaitOne();
} public void Leave()
{
if (Interlocked.Decrement(ref m_waiters) == )
return; m_waiterLock.Set();
} public void Dispose() { m_waiterLock.Dispose(); }
}

例子很简单,初次使用用户模式判断;若有线程竞争者,则使用内核模式的进行线程阻塞。 
在混合线程同步构造中有四个性能考虑点:内核对象的创建、Dispose、Enter方法、Leave方法。其中可主要考虑Enter及Leave方法。但是在.NET中其也提供了AutoResetEventSlim构造,其使用了“延迟加载”方法,即,只有当内核对象初次使用时(即第一次检测到竞争时),才会创建AutoResetEvent,这样可以避免性能损失。 
上面的例子中,任何线程都可以调用Leave方法。所以这方法不够严谨。因此可以在Enter及Leave方法中添加相关用于记录获取同步锁的线程信息的字段,这样就能保证做到只有获得同步锁的线程才能调用Leave方法。下面的例子进行了说明:

    internal sealed class AnotherHybridLock : IDisposable
{ private int m_waiters = ;
private AutoResetEvent m_waiterLock = new AutoResetEvent(false);
private int m_spincount = ;
private int m_owningTheadID = , m_recursion = ; public void Enter()
{
//若相同的线程调用Enter方法,则增加一次循环记录后,返回
int threadID = Thread.CurrentThread.ManagedThreadId;
if (threadID == m_owningTheadID)
{
m_recursion++;
return;
} //若第一个线程使用通过内核模式获得同步锁后,紧随其后的第二个线程并不会立刻调用内核模式
//而是通过一个循环,碰碰运气,看能否在循环内得到同步锁
SpinWait spinWait = new SpinWait();
for (int spinCount = ; spinCount < m_spincount;spinCount++ )
{
if (Interlocked.CompareExchange(ref m_waiters, , ) == ) goto GotLock; spinWait.SpinOnce();
} //若还是没有得到,只能调用内核模式,等待获取同步锁
if (Interlocked.Increment(ref m_waiters)>)
{
m_waiterLock.WaitOne();
} GotLock:
m_owningTheadID = threadID; m_recursion = ;
} public void Leave()
{
int threadID = Thread.CurrentThread.ManagedThreadId;
if (threadID!=m_owningTheadID)
{
throw new SynchronizationLockException("Leave被非原线程调用!");
} //代表同一线程多次调用Leave方法,则进行--m_recursion后,直接返回
if (--m_recursion > ) return; //代表调用Leave的线程目前只有一次Enter,因此调用Leave方法释放同步锁
//将当前的线程ID置位0
m_owningTheadID = ; //代表外界无等待的线程,则直接返回
if (Interlocked.Decrement(ref m_waiters) == ) return; //代表外界存在等在同步锁的线程,则通过内核方法,释放同步锁
//使等待线程获取同步锁,解除阻塞
m_waiterLock.WaitOne();
} public void Dispose() { m_waiterLock.Dispose(); }
}

在上面的两个例子中:有一个特点:用户模式只能提供一个同步锁,若还有多线程同时访问锁,则使用内核模式。这样才能发挥用户模式的“快”和内核模式的“省”。另外,第二个例子(AnotherHybridLock)与第一个相比:1、对象占用内存要大;2、Enter与Leave的性能要低。鱼与熊掌不可兼得嘛!

SpinWait结构

在后一个例子中有这样一段代码:

SpinWait spinWait = new SpinWait();
for (int spinCount = ; spinCount < m_spincount;spinCount++ )
{
if (Interlocked.CompareExchange(ref m_waiters, , ) == ) goto GotLock;
spinWait.SpinOnce();
}

这里面有一个SpinWait结构(其实还有一个Thread.SpinWait方法),对这个结构比较感兴趣,所以就去看了看MSDN的解释。 
SpinWait结构是一种可以在小脚本(low-level scenarios)中使用,并且可以避免上下文切换和内核转换的轻量级类型。说白了,其是一种“智能”的自旋方式(这里的智能是指,内部添加了一些算法,使自旋不仅仅是简单的自旋,还有一些其他的功能,帮助提升性能)。另外,SpinWait在自旋一段时间后,也会让出CPU(并不是一直在自旋),这样CPU可以处理其他的线程,而不是傻傻的一直等待自旋结束。 
SpinWait结构的属性和方法如下: 
 
 
这么多属性和方法中只有两个最常用,一个是属性:NextSpinWillYield;一个是方法:SpinOnce() 
SpinOnce()方法:方法内部有一个if…else…判断。如果NextSpinWillYield返回true,则方法内部通过调用Thread.Sleep(0)、Thread.Sleep(1)、Thread.Yield()方法让出CPU,否则调用Thread.Spinwait()方法使其继续自旋。 
NextSpinWillYield:其决定了调用SpinOnce方法的线程是否应该让出CPU。若返回true,则调用SpinOnce()方法的线程会让出CPU;返回false,则线程仍将自旋。 
下面通过一个例子说明:

   class Program
{
static void Main(string[] args)
{
bool someBoolean = false;
int numYields = ; //线程1
Task t1 = Task.Factory.StartNew(() =>
{
SpinWait sw = new SpinWait();
while (!someBoolean)
{
//NextSpinWillYield属性返回true,则调用SpinOnce方法的线程会让出CPU
//否则,自旋
if (sw.NextSpinWillYield) numYields++;
sw.SpinOnce();
} Console.WriteLine("SpinWait called {0} times, yielded {1} times", sw.Count, numYields);
}); //第二个任务,在0.1秒后将someBoolean置为true
Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep();
someBoolean = true;
}); //等待两个任务完成
Task.WaitAll(t1, t2);
Console.ReadLine();
}
}

SpinWait结构源码

下面通过SpinWait结构的源代码进行说明 
 
这里面有一个内部变量m_count,其用来记录SpinOnce方法的调用次数。 
先看一下属性NextSpinWillYield的源代码:

public bool NextSpinWillYield
{
get
{
if (this.m_count <= )
{
return PlatformHelper.IsSingleProcessor;
}
return true;
}
}

哈哈哈,其逻辑就是:若调用SpinOnce方法10次以内,看看电脑是否为单核电脑。否则就返回true。 
再看看SpinOnce()的源代码:

public void SpinOnce()
{
//在方法内部,其还是调用一次NextSpinWillYield属性,根据属性的结果,决定是让出CPU还是自旋 if (this.NextSpinWillYield) //为true,则代表让出CPU。只不过,需要根据m_count的值决定使用何种方法
{
CdsSyncEtwBCLProvider.Log.SpinWait_NextSpinWillYield();
int num = (this.m_count >= ) ? (this.m_count - ) : this.m_count;
if ((num % ) == 0x13)
{
Thread.Sleep();
}
else if ((num % ) == )
{
Thread.Sleep();
}
else
{
Thread.Yield();
}
}
else
{
//让CPU进行时间为:this.m_count*16的自旋
Thread.SpinWait(((int) ) << this.m_count);
}
//0x7fffffff,其为int32的最大值。即,若m_count到了最大值后,从10开始。这样NextSpinWillYield将会一直返回true
this.m_count = (this.m_count == 0x7fffffff) ? : (this.m_count + );
}

以上就是SpinWait的源代码内容。 
另外,在SpinWait结构内容使用了Thread.Sleep(1)、Thread.Sleep(0)、Thread.Yield()方法,这三者方法具体的差别可参见一篇博客:三个方法的具体区别

[.net 多线程]SpinWait的更多相关文章

  1. 重新想象 Windows 8 Store Apps 系列文章索引

    [源码下载][重新想象 Windows 8.1 Store Apps 系列文章] 重新想象 Windows 8 Store Apps 系列文章索引 作者:webabcd 1.重新想象 Windows ...

  2. Windows 8 Store Apps

    重新想象 Windows 8 Store Apps 系列文章索引 Posted on 2013-11-18 08:33 webabcd 阅读(672) 评论(3) 编辑 收藏 [源码下载] 重新想象 ...

  3. 重新想象 Windows 8 Store Apps (48) - 多线程之其他辅助类: SpinWait, SpinLock, Volatile, SynchronizationContext, CoreDispatcher, ThreadLocal, ThreadStaticAttribute

    [源码下载] 重新想象 Windows 8 Store Apps (48) - 多线程之其他辅助类: SpinWait, SpinLock, Volatile, SynchronizationCont ...

  4. 《C#多线程编程实战》2.10 SpinWait

    emmm 这个SpinWait 中文是自旋等待的意思. 所谓自旋,就是自己追自己影子,周伯通的左右手互博,不好听就是放屁自己追着玩,小狗转圈咬自己的尾巴 SpinWait是一个结构体,并不是一个类. ...

  5. C#多线程之线程同步篇3

    在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...

  6. C#多线程之线程同步篇1

    在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示: 执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 ...

  7. C# - 多线程 之 Process与Thread与ThreadPool

    Process 进程类, // 提供对本地和远程进程的访问,启动/停止本地系统进程 public class Process : Component { public int Id { get; } ...

  8. C#基础系列——多线程的常见用法详解

    前言:前面几节分别介绍了下C#基础技术中的反射.特性.泛型.序列化.扩展方法.Linq to Xml等,这篇跟着来介绍下C#的另一基础技术的使用.最近项目有点紧张,所以准备也不是特别充分.此篇就主要从 ...

  9. C#多线程技术总结(同步)

    二.串行(同步): 1.lock.Monitor--注意锁定的对象必需是引用类型(string类型除外) 示例: private static object syncObject = new obje ...

随机推荐

  1. Linux:课程安排、Linux简介、虚拟机安装、课前准备(常用设置和操作)

    一.课程安排 1)Linux 的作用 商业服务器基本上都是 Linux: 开源软件都先支持 Linux: 大数据分析.机器学习首先选 Linux: 整个互联网地基靠Linux撑起来: Linux 系统 ...

  2. java里的MouseLisetener接口的使用过程==========需要用组件是来注册侦听器

    总结:通过匿名类来实现鼠标的监听或者  通过实现接口的方法都可以的 从此是实现MouseListener接口的方式 package com.a.b; import java.awt.Color; im ...

  3. Mybatis拦截器介绍及分页插件

    1.1    目录 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 1.2     前言 拦截器的一 ...

  4. Java面试(4)

    1 哈希函数满足什么条件? 计算简单/散列地址分布均匀. 2 hash处理冲突方式?HashMap采用的什么办法? 开放地址法: 线性探测 . 线性补偿法.伪随机数法. 拉链法:HashMap采用链地 ...

  5. PCB上 如何显示 汉字

    原理图上有汉字,那如何在PCB上显示汉子呢  ?  而不至于显示乱码 按如下操作  ,双击乱码  ,进入设置模式 设置好后,显示的字体样式.

  6. Lua文件操作和串行化

    function n_serialize(data) if type(data)=="number" then io.write(data,"\n") else ...

  7. WP8.1通过StreamSocket连接C++服务器

    注:当服务端和手机模拟器运行在一台机器时,会有奇怪错误.将服务端放在其它机器上更改客户端连接地址,运行正常.或者直接用本机modern调试也可以. 实例化一个对象 StreamSocket clien ...

  8. Py修行路 python基础 (二十五)线程与进程

    操作系统是用户和硬件沟通的桥梁 操作系统,位于底层硬件与应用软件之间的一层 工作方式:向下管理硬件,向上提供接口 操作系统进行切换操作: 把CPU的使用权切换给不同的进程. 1.出现IO操作 2.固定 ...

  9. CentOS-6.4 编译安装ffmpeg加x264以及rtmp

    CentOS 6.4-64位下编译ffmpeg几个简单步骤: 1.编译前环境准备: 2.下载源码: 3.编译,安装: ----------------------------------------- ...

  10. Linux主机名的设置

    Linux主机名的设置 Linux主机名的设置步骤如下: 1.在/etc/sysconfig/network文件中修改HOSTNAME的值为要设置的主机名. HOSTNAME=myhost 2.在/e ...