一、前言
本文章汇总c#中常见的锁,基本都列出了该锁在微软官网的文章,一些不常用的锁也可以参考微软文章左侧的列表,方便温习回顾。
二、锁的分类
2.1、用户模式锁
1、volatile 关键字
volatile 并没有实现真正的线程同步,操作级别停留在变量级别并非原子级别,对于单系统处理器中,变量存储在主内存中,没有机会被别人修改。但是如果是多处理器,可能就会有问题,因为每个处理器都有单独的data cache,数据更新不一定立刻被写回到主存,可能会造成不同步。
参考:valatile 微软官网文章。
2、Spinlock 旋转锁
Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方式解决资源冲突的,即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁则获取不到,只能够原地“打转”(忙等待)。由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 :自旋锁不应该被长时间的持有(消耗 CPU 资源)。
参考:Spinlock 微软官网文章。
2.2、内核模式锁
1、事件锁
自动事件锁:AutoResetEvent
WaitOne()进入等待,Set()会释放当前锁给一个等待线程。
var are = new AutoResetEvent(true); are.WaitOne(); //... are.Set();
手动事件锁:ManualResetEvent
WaitOne()进入等待,Set()会释放当前锁给所有等待线程。
var mre = new ManualResetEvent(false); mre.WaitOne();//批量拦截,后续的省略号部分是无序执行的。 //... mre.Set();//一次释放给所有等待线程
参考:ManuaResetEvent 微软官网文章。
2、信号量
信号量:Semaphore
信号量可以控制同时通过的线程数以及总的线程数。
//第一个参数表示同时可以允许的线程数,比如1表示每次只允许一个线程通过, //第二个是最大值,比如8表示最多有8个线程。 var semaphore = new Semaphore(1, 8);
参考:Semaphore 微软官网文章。
3、互斥锁
互斥锁:Mutex
Mutex和Monitor很接近,但是没有Monitor.Pulse,Wait,PulseAll的唤醒功能,他的优点是可以跨进程,可以在同一台机器甚至远程机器人的不同进程间共用一个互斥体。
var mutex = new Mutex(); mutex.WaitOne(); //... mutex.ReleaseMutex();
参考:Mutex 微软官网文章。
4、读写锁
读写锁:ReaderWriterLock
不要使用ReaderWriterLock,该类有问题(死锁、性能),请使用ReaderWriterLockSlim
.NET Framework有两个读取器-编写器锁,ReaderWriterLockSlim以及ReaderWriterLock。 建议对所有新开发的项目使用 ReaderWriterLockSlim。 虽然 ReaderWriterLockSlim 类似于 ReaderWriterLock,但不同之处在于,前者简化了递归规则以及锁状态的升级和降级规则。 ReaderWriterLockSlim 避免了许多潜在的死锁情况。 另外,ReaderWriterLockSlim 的性能显著优于 ReaderWriterLock。
参考:ReaderWriterLock 微软官网文章。
读写锁:ReaderWriterLockSlim
//源码摘录自微软官网 using System; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; public class SynchronizedCache { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private Dictionary<int, string> innerCache = new Dictionary<int, string>(); public int Count { get { return innerCache.Count; } } 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 void Delete(int key) { cacheLock.EnterWriteLock(); try { innerCache.Remove(key); } finally { cacheLock.ExitWriteLock(); } } public enum AddOrUpdateStatus { Added, Updated, Unchanged }; ~SynchronizedCache() { if (cacheLock != null) cacheLock.Dispose(); } }
参考:ReaderWriterLockSlim 微软官网文章。
2.3、动态计数锁
1、动态计数锁:CountdownEvent
限制线程数的一个机制,而且这个也是比较常用的(同属于信号量的一种)。
var cde = new CountdownEvent(10); //重置当前ThreadCount上限 cde.Reset(10); for(int i=0; i<10; i++) { Task.Factory.StartNew(()=> { Thread.Sleep(1000); SubWoker1(); }); } cde.Wait();//相当于Task.WaitAll() cde.Reset(8); for(int i=0; i<8; i++) { Task.Factory.StartNew(()=> { Thread.Sleep(1000); SubWoker2(); }); } cde.Wait();//相当于Task.WaitAll() static void SubWoker1() { //... cde.Signal();//将当前的ThreadCount-1操作。 } static void SubWoker2() { //... cde.Signal();//将当前的ThreadCount-1操作。 }
参考:CountdownEvent 微软官网文章。
2、原子操作类:Interlocked
Interlocked类则提供了4种方法进行原子级别的变量操作。Increment , Decrement , Exchange 和CompareExchange 。
a、使用Increment 和Decrement 可以保证对一个整数的加减为一个原子操作。
b、Exchange 方法自动交换指定变量的值。
c、CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。
d、比较和交换操作也是按原子操作执行的。Interlocked.CompareExchange(ref a, b, c); 原子操作,a参数和c参数比较, 相等b替换a,不相等不替换。
参考:Interlocked 微软官网文章。
2.4、监视锁
1、监视锁:Monitor
Monitor锁为操作的代码块添加互斥对象,如果A线程正在访问,对象没有到达临界区,则B线程不会访问。
参考:Monitor 微软官网文章。
2、监视锁:lock
lock锁可以视为monitor锁的语法糖,增加了自动释放机制和异常处理机制。
a、不推荐使用lock(this)的方式作为lock锁,因为你不确定别的地方是否重新实例了含有lock的对象。
b、不要lock一个字符串。
c、不要lock一个外部公开变量。