【C#进阶系列】29 混合线程同步构造
上一章讲了基元线程同步构造,而其它的线程同步构造都是基于这些基元线程同步构造的,并且一般都合并了用户模式和内核模式构造,我们称之为混合线程同步构造。
在没有线程竞争时,混合线程提供了基于用户模式构造所具备的性能优势,而多个线程竞争一个构造时,混合线程通过基元内核模式的构造来提供不“自旋”的优势。
那么接下来就是个简单的混合线程同步构造的例子,可与上一章最后的那些例子相比较:
public class SimpleHybridLock : IDisposable {
private Int32 m_waiters = ;
private AutoResetEvent m_waiterlock = new AutoResetEvent(false);//注意这里是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();
}
}
上面的例子学了上一张后看起来感觉很简单就不讲解了,只是一个简单的,将Interlocked这种互锁构造和自动重置事件构造AutoResetEvent 结合起来的,混合线程同步构造的例子。
上面混合锁可以去加入自旋,当超过一定的自旋次数时再进行阻塞。也可以去加入互斥体的递归玩法,总之这个东西充满了无限的可能。
.NET 框架类库中的混合构造
总体而言,实际上就是对上面那个简单例子的扩展,它们的目的都是为了使线程能尽可能不去进入内核模式,并且减少线程竞争时自旋的性能影响。
- ManualResetEventSlim类和SemaphoreSlim类
- 翻译过来就是手工重置事件简化构造和信号量简化构造
- 发生第一次竞争时才进行内核模式构造,否则为用户模式构造
- 可传递超时值和CancellationToken,也就是取消啦,信号量那个还能进行异步等待。
- Monitor类和同步块
- Monitor类是最常用的,支持递归,线程所有权和互斥
- 然而这个类存在一些问题,容易引发BUG。因为它是一个静态类,它的正确玩法在一定程度上和其它同步构造有所区别。
- 堆中的每个对象都可以关联一个叫同步块的数据结构,它为内核对象,且拥有线程ID,递归计数,等待线程计数。而Monitor类的操作就涉及到这些同步块的字段。
- 每个对象都有一个同步块索引,而同步块实际上是在CLR初始化的时候就创建的一个同步块数组中。
- 一个对象在构造时它的同步块索引为-1,就是没有关联任何同步块。而调用Monitor.Enter后CLR在同步块数组中找到个空白同步块,并设置对象的同步块索引,让它引用该同步块。Exit当然就是取消关联。
- Monitor.Enter会传一个对象进去,这个对象必须为所在函数的类的私有对象,而不能传所在对象本身,这回让这个锁变成公共的。这样就会引发很多问题。所以最好的方法就是传递一个私有的只读对象。
- 永远不要讲String,值类型和类型对象传给Monitor.Enter。
- 而C#有一个lock关键字提供的简化语法就是基于Monitor的。而且其相当于在一个try finally结构上使用。首先不利于性能,其次还可能造成线程访问损坏的状态。所以作者建议杜绝使用lock语法。
- LockToken变量默认false,只有在Enter调用后才为true,要是在Enter调用前Exit,可以考虑判断LockToken,从而避免错误的Exit。
- ReaderWriterLockSlim类
- 它的特点:
- 一个线程向数据写入时,请求访问的其他所有线程都被阻塞
- 一个线程从数据读取时,请求读取的其它线程允许继续执行,但请求写入的线程仍被阻塞。
- 向线程写入的线程结束后,要么解除一个写入线程的阻塞,使它能向数据写入,要么解除所有读取线程的阻塞,使它们能并发读取数据。如果没有线程被阻塞,锁就进入可以自由使用的状态,可供下一个reader或writer线程获取。
- 从数据读取所有线程结束后,一个writer线程被解除阻塞,使它能向数据写入。如果没有线程被阻塞,锁就进入可以自由使用的状态,可供下一个reader或writer线程获取。
- 根据以上特点有EnterReadLock和EnterWriteLock两种玩法,两种玩法跟之前的那些例子都类似,只是效果不同,这里就不举例了。
- 它的特点:
虽然提供了这么多同步构造,且玩法也很多。但是最重要的还是一点:能尽量避免就避免阻塞线程,否则应尽量使用Volatile和Interlocked方法,因为它们速度快,然而这两个只能操作简单类型。
一定要阻塞,就可以使用Monitor类,也可以用ReaderWriterLockSlim类,虽然比Monitor慢,但是允许多个线程并发进行,提升了总体性能,减少阻塞线程的几率。
用System.Lazy类或者System.Threading.LazyInitializer类去替代双检索玩法。
一句话解决这个点:
Lazy<String> s=new Lazy<String>(()=>DateTime.Now.ToLongTimeString(),true);
调用的话就用s.Value,实际上就是封装了双检索,有些地方加了些优化。目的就是延时加载。
异步锁
其实叫异步的同步构造,因为一般的同步构造都是用阻塞线程或者自旋来完成,而异步锁的目的就是为了不阻塞来玩。
SemaphoreSlim类的WaitAsync方法就是这个思路,信号量玩法而已。
而reader-writer语义的玩法是ConcurrentExclusiveSchedulerPair类。(当没有ConcurrentScheduler任务时,使用ExclusiveScheduler为独占式运行。没有ExclusiveScheduler运行时,ConcurrentScheduler调度的任务可同时进行)
并发集合类
FCL自带四个线程安全的集合类,全在System.Collections.Concurrent(Concurrent为并发的意思)命名空间中定义。
它们是ConcurrentQueue,ConcurrentStack,ComcurrentDictionary和ConcurrentBag。
所有这些都是“非阻塞“的。(实际上在ConcurrentQueue,ConcurrentStack和ConcurrentBag为空的时候还要提取数据,那么提取数据的这个线程就会被阻塞)
【C#进阶系列】29 混合线程同步构造的更多相关文章
- CLR via C# 混合线程同步构造
1. 自旋,线程所有权和递归 2. 混合构造 a.ManualResetEventSlim b.SemaphoreSlim c.Monitor d.ReaderWriterLockSlim 3.条件变 ...
- 【C#进阶系列】28 基元线程同步构造
多个线程同时访问共享数据时,线程同步能防止数据损坏.之所以要强调同时,是因为线程同步问题实际上就是计时问题. 不需要线程同步是最理想的情况,因为线程同步一般很繁琐,涉及到线程同步锁的获取和释放,容易遗 ...
- Clr Via C#读书笔记----基元线程同步构造
线程文章:http://www.cnblogs.com/edisonchou/p/4848131.html 重点在于多个线程同时访问,保持线程的同步. 线程同步的问题: 1,线程同步比较繁琐,而且容易 ...
- 基元线程同步构造之waithandle中 waitone使用
在使用基元线程同步构造中waithandle中waitone方法的讲解: 调用waithandle的waitone方法阻止当前线程(提前是其状态为Nonsignaled,即红灯),直到当前的 Wait ...
- [.net]基元线程同步构造
/* 基元线程同步构造 用户模式构造: 易变构造(Volatile Construct) 互锁构造(Interlocked Construct):自旋锁(Spinlock) 乐观锁(Optimisti ...
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- Java多线程系列三——实现线程同步的方法
两种实现线程同步的方法 方法 特性 synchronized 不需要显式地加解锁,易实现 ReentrantLock 需要显式地加解锁,灵活性更好,性能更优秀,结合Condition可实现多种条件锁 ...
- 基元线程同步构造之 Mutes(互斥体)
互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex)). 互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section). 因 ...
- 基元线程同步构造 AutoResetEvent和ManualResetEvent 线程同步
在.Net多线程编程中,AutoResetEvent和ManualResetEvent这两个类经常用到, 他们的用法很类似,但也有区别.ManualResetEvent和AutoResetEvent都 ...
随机推荐
- 在职场中混,"讲演稿"的重要性
背景: 在职场上工作的人,思维都像流水行云一样无拘无束.某时某刻说不定就会产生点“头脑风暴”. 但是在多数情总下,只是自己想想,跟朋友吹瞌子的时候随便说说,很少有用文字,图形把自己的思维给表现出来. ...
- [Oracle](不会的是三炮)把状态列表作为存储过程参数这件小事
抱歉用了这么渣的标题,其实是一个很简单而且很常见的需求:假设我们有一个学生表,它有一个状态字段: create table T_STU ( STU_ID ) not null, NAME ), COD ...
- 架构设计(ASP.NET MVC+Knockout+Web API+SignalR)
最近忙于重构项目的架构设计,没有时间发博客,也没有时间回复邮件及博文评论,忘各位见谅: 今天先发布架构设计图,同样没有时间写相关的介绍也没有时间回复评论,所以就不发在首页,希望给看到的朋友一些参考,同 ...
- android服务里生成通知点击后返回正在运行的程序和当前的Activity
想在服务里生成一个通知,并且点击通知打开当前应用程序下单当前活动,折腾了半天,网上的那些都不靠谱,试了半天,最后把ActivityManager和反射都用进来了,终于解决了这个问题.这样在服务中想恢复 ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(十七):个性化菜单接口说明
前不久微信上线了个性化菜单接口,Senparc.Weixin SDK也已经同步更新. 本次更新升级Senparc.Weixin.MP版本到v13.5.2,依赖Senparc.Weixin版本4.5.4 ...
- Module Zero之角色管理
返回<Module Zero学习目录> 角色实体 角色管理者 多租户 角色实体 角色实体代表了该应用的一个角色.它应该派生自AbpRole类,如下所示: public class Role ...
- Spark读取HBase
背景:公司有些业务需求是存储在HBase上的,总是有业务人员找我要各种数据,所以想直接用Spark( shell) 加载到RDD进行计算 摘要: 1.相关环境 2.代码例子 内容 1.相关环境 Spa ...
- 再谈使用Emit把Datatable转换为对象集合(List<T>)
一.前因和存在的问题 前面我写了一篇<使用Emit把Datatable转换为对象集合(List<T>)>的博文,其实起源于我自己编写的一个orm工具(见前面几篇博文有介绍),里 ...
- CI Weekly #3 | 关于微服务、Docker 实践与 DevOps 指南
CI Weekly 围绕『 软件工程效率提升』 进行一系列技术内容分享,包括国内外持续集成.持续交付,持续部署.自动化测试. DevOps 等实践教程.工具与资源,以及一些工程师文化相关的程序员 Ti ...
- Spring学习记录(四)---bean之间的关系:继承、依赖
继承 这里说的继承和java的继承是不一样的,不是父类子类.但思想很相似,是父bean和子bean 1.父bean是一个实例时.它本身是一个完整的bean 2.父bean是模板,抽象bean ...