详解C#多线程之线程同步

2019-12-30 16:02:53王振洲

最后一个volatile构造是在.NET Framework中新增的,里面包含的方法都是Read和Write,它实际上就相当于Thread的VolatileRead 和VolatileWrite 。这需要拿源码来说明了,随便拿一个Volatile的Read方法来看

C#,线程同步

而再看看Thraed的VolatileRead方法

C#,线程同步

另一个用户模式构造是Interlocked,这个构造是保证读和写都是在原子操作里面,这是与上面volatile最大的区别,volatile只能确保单纯的读或者单纯的写。

为何Interlocked是这样,看一下Interlocaked的方法就知道了


Add(ref int,int)// 调用ExternAdd 外部方法
CompareExchange(ref Int32,Int32,Int32)//1与3是否相等,相等则替换2,返回1的原始值
Decrement(ref Int32)//递减并返回 调用add
Exchange(ref Int32,Int32)//将2设置到1并返回
Increment(ref Int32)//自增 调用add

就随便拿其中一个方法Add(ref int,int)来说(Increment和Decrement这两个方法实际上内部调用了Add方法),它会先读到第一个参数的值,在与第二个参数求和后,把结果写到给第一参数中。首先这整个过程是一个原子操作,在这个操作里面既包含了读,也包含了写。至于如何保证这个操作的原子性,估计需要查看Rotor源码才行。在代码优化方面来说,它确保了所有写操作都在Interlocked之前去执行,这保证了Interlocked里面用到的值是最新的;而任何变量的读取都在Interlocked之后读取,这保证了后面用到的值都是最新更改过的。

CompareExchange方法相当重要,虽然Interlocked提供的方法甚少,但基于这个可以扩展出其他更多方法,下面就是个例子,求出两个值的最大值,直接抄了Jeffrey的源码

C#,线程同步

查看上面代码,在进入循环之前先声明每次循环开始时target的值,在求出最值之后,核对一下target的值是否有变化,如果有变化则需要再记录新值,按照新值来再求一次最值,直到target不变为止,这就满足了Interlocked中所说的,写都在Interlocked之前发生,Interlocked往后就能读到最新的值。

基元内核模式

内核模式则是靠操作系统的内核对象来处理线程的同步问题。先说其弊端,它的速度会相对慢。原因有两个,其一由于它是由操作系统内核对象来实现的,需要操作系统内部去协调,另外一个原因是内核对象都是一些非托管对象,在了解了AppDomain之后就会知道,访问的对象不在当前AppDomain中的要么就进行按值封送,要么就进行按引用封送。经过观察这部分的非托管资源是按引用封送,这就会存在性能影响。综合上面两方面的两点得出内核模式的弊端。但是他也是有利的方面:1.线程在等待资源的时候不会"自旋"而是阻塞,这个节省了CPU时间,并且这个阻塞可以设定一个超时值。2.可以实现Window线程和CLR线程的同步,也可同步不同进程中的线程(前者未体验到,而对于后者则知道semaphores中有边界值资源)。3.可应用安全性设置,为经授权账户禁止访问(这个不知道是咋回事)。