C#多线程编程中的锁系统(二)
上章主要讲排他锁的直接使用方式。但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了。 这一次我们说说升级锁和原子操作。 目录 一:volatile 简单来说: volatile关键字是告诉c#编译器和JIT编译器,不对volatile标记的字段做任何的缓存。确保字段读写都是原子操作,最新值。 这不就是锁吗? 其这货它根本不是锁, 它的原子操作是基于CPU本身的,非阻塞的。 因为32位CPU执行赋值指令,数据传输最大宽度4个字节。 所以只要在4个字节以下读写操作的,32位CPU都是原子操作。volatile 它就是利用这个特性来的。 好残酷的事实?不然,微软大法这样是为了提高JIT性能效率,对有些数据进行缓存了(多线程下)。 复制代码 代码如下: //正确 public volatile Int32 score1 = 1; //报错 public volatile Int64 score2 = 1; 看上面的例子,我们定义8个字节长度score2就不行了。 因为8个字节,32位CPU就分成2个指令执行了。自然就无法保证原子操作了。 这么细节的,忘了怎么办,那岂不是坑人啊。 于是微软大法直接一棍子打死,限制4个字节以下的类型字段才能用volatile,具体什么、看msdn吧。 那今天我知道了。我编译平台改成64位上,只在64位CPU用volatile int64,行不行? 不行,编译器报错。说了一棍子打死了。。 (^._.^)ノ 好吧,其实可以用IntPtr这个。 volatile多数情况下很有用处的,毕竟锁的性能开销还是很大的。我们可以把当成轻量级的锁,根据具体场景合理使用,能提高不少程序性能。 线程中的Thread.VolatileRead 和Thread.VolatileWrite 就是volatile的复杂版。 二:Interlocked MSDN 描述:为多个线程共享的变量提供原子操作。主要函数如下: Interlocked.Increment 原子操作,递增指定变量的值并存储结果。 Interlocked.CompareExchange(ref a,b,c); 原子操作,a参数和c参数比较, 相等b替换a,不相等不替换。 基本用法就不多说了。直接来段CLR via C# interlock anything的例子: 复制代码 代码如下: public static int Maximum(ref int target,int value) { int currentVal = target,startVal,desiredVal; //记录前后值 do { startVal = currentVal; //记录循环迭代的初始值。 desiredVal = Math.Max(startVal,value); //基于startVal和value计算期望值desiredVal //高并发下,线程被抢占情况下,target值会发生改变。 //target startVal相等说明没改变。desiredVal 直接替换。 } while (startVal != currentVal); //不相等说明,target值已经被其他线程改动。自旋继续。 三:ReaderWriterLockSlim 假如我们有份缓存数据A,如果每次都不管任何操作lock一下,那么我的这份缓存A就永远只能单线程读写了, 这在Web高并发下是不能忍受的。 那有没有一种办法我只在写入时进入独占锁呢,读操作时不限制线程数量呢?答案就是我们的ReaderWriterLockSlim主角,读写锁。 ReaderWriterLockSlim 其中一种锁EnterUpgradeableReadLock最关键 即可升级锁。 它呢允许你先进入读锁,发现缓存A不一样了, 再进入写锁,写入后退回读锁模式。 ps: 这里注意下net 3.5之前有个ReaderWriterLock 性能较差。推荐使用升级版的 ReaderWriterLockSlim 。 复制代码 代码如下: //实例一个读写锁 ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); 上面实例一个读写锁,这里注意的是构造函数的枚举。 LockRecursionPolicy.NoRecursion 不支持,发现递归会抛异常。 LockRecursionPolicy.SupportsRecursion 即支持递归模式,线程锁中继续在使用锁。 复制代码 代码如下: cacheLock.EnterReadLock(); //do cacheLock.EnterReadLock(); //do cacheLock.ExitReadLock(); cacheLock.ExitReadLock(); 这种模式极易容易死锁,比如读锁里面使用写锁。 复制代码 代码如下: cacheLock.EnterReadLock(); //do cacheLock.EnterWriteLock(); //do cacheLock.ExitWriteLock(); cacheLock.ExitReadLock(); 下面是直接拿msdn的缓存例子了,加了简单注释。 复制代码 代码如下: public class SynchronizedCache { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private Dictionary<int,string> innerCache = new Dictionary<int,string>(); public string Read(int key) public void Add(int key,string value) public bool AddWithTimeout(int key,string value,int timeout) public AddOrUpdateStatus AddOrUpdate(int key,string value) public enum AddOrUpdateStatus 四:总结 多线程实际开发当中,往往测试没问题,一到生产环境,并发高了就容易出问题, 一定注意。 本文参考CLR via C#。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |