上章主要讲排他锁的直接使用方式。但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了。 这一次我们说说升级锁和原子操作。
目录
1:volatile
2: Interlocked
3:ReaderWriterLockSlim
4:总结
一: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.Decrement 原子操作,递减指定变量的值并存储结果。
Interlocked.Add 原子操作,添加两个整数并用两者的和替换第一个整数
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 直接替换。 currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);
} while (startVal != currentVal); //不相等说明,target值已经被其他线程改动。自旋继续。 return desiredVal; }
三: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) { //进入读锁,允许其他所有的读线程,写入线程被阻塞。 cacheLock.EnterReadLock(); try { return innerCache[key]; } finally { cacheLock.ExitReadLock(); } }
public void Add(int key, string value) { //进入写锁,其他所有访问操作的线程都被阻塞。即写独占锁。 cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } }
public bool AddWithTimeout(int key, string value, int timeout) { //超时设置,如果在超时时间内,其他写锁还不释放,就放弃操作。 if (cacheLock.TryEnterWriteLock(timeout)) { try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return true; } else { return false; } }
public AddOrUpdateStatus AddOrUpdate(int key, string value) { //进入升级锁。 同时只能有一个可升级锁线程。写锁,升级锁都被阻塞,但允许其他读取数据的线程。 cacheLock.EnterUpgradeableReadLock(); try { string result = null; if (innerCache.TryGetValue(key, out result)) { if (result == value) { return AddOrUpdateStatus.Unchanged; } else { //升级成写锁,其他所有线程都被阻塞。 cacheLock.EnterWriteLock(); try { innerCache[key] = value; } finally { //退出写锁,允许其他读线程。 cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Updated; } } else { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Added; } } finally { //退出升级锁。 cacheLock.ExitUpgradeableReadLock(); } }
public enum AddOrUpdateStatus { Added, Updated, Unchanged }; }
四:总结
多线程实际开发当中,往往测试没问题,一到生产环境,并发高了就容易出问题, 一定注意。
本文参考CLR via C#。
本文向大家介绍C#多线程编程中的锁系统(三),包括了C#多线程编程中的锁系统(三)的使用技巧和注意事项,需要的朋友参考一下 本章主要说下基于内核模式构造的线程同步方式,事件,信号量。 目录 一:理论 二:WaitHandle 三:AutoResetEvent 四:ManualResetEvent 五:总结 一:理论 我们晓得线程同步可分为,用户模式构造和内核模式构造。 内核模式构造:是由windo
本文向大家介绍C#多线程编程中的锁系统(四):自旋锁,包括了C#多线程编程中的锁系统(四):自旋锁的使用技巧和注意事项,需要的朋友参考一下 目录 一:基础 二:自旋锁示例 三:SpinLock 四:继续SpinLock 五:总结 一:基础 内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式。用户模式构造和内核模式构造 优点:cpu利用最大化。它发现资源被锁住,请求就排
本文向大家介绍C#多线程编程中的锁系统基本用法,包括了C#多线程编程中的锁系统基本用法的使用技巧和注意事项,需要的朋友参考一下 平常在多线程开发中,总避免不了线程同步。本篇就对net多线程中的锁系统做个简单描述。 目录 一:lock、Monitor 1:基础。 2: 作用域。 3:字符串锁。 4:monitor使用 二:mutex 三:Semaphore 四
问题内容: 我正在努力加快某些过程的执行速度,这些过程将大量记录(大多数是几百万个)发布到Elasticsearch。在我的C#代码中,我已经使用Dataflow实现了一个多线程解决方案,如下所示: 然后我要实现的发送批量请求调用: 我的问题 ,你 是对的实用性存在的数据流管道的一部分的锁内执行额外的线程。 这个可以吗?我可以在性能,执行,缓存/内存丢失等方面看到任何潜在的问题吗? 任何见识都会很
本文向大家介绍Python多线程编程(五):死锁的形成,包括了Python多线程编程(五):死锁的形成的使用技巧和注意事项,需要的朋友参考一下 前一篇文章Python:使用threading模块实现多线程编程四[使用Lock互斥锁]我们已经开始涉及到如何使用互斥锁来保护我们的公共资源了,现在考虑下面的情况– 如果有多个公共资源,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待
本文向大家介绍linux下c语言的多线程编程,包括了linux下c语言的多线程编程的使用技巧和注意事项,需要的朋友参考一下 我们在写linux的服务的时候,经常会用到linux的多线程技术以提高程序性能 多线程的一些小知识: 一个应用程序可以启动若干个线程。 线程(Lightweight Process,LWP),是程序执行的最小单元。 一般一个最简单的程序最少会有一个线程,就是程序本身,也就是