ReaderWriterLockSlim 類
表示用於管理資源訪問的鎖定狀態,可實現多線程讀取或進行獨占式寫入訪問。
使用 ReaderWriterLockSlim 來保護由多個線程讀取但每次只采用一個線程寫入的資源。 ReaderWriterLockSlim 允許多個線程均處於讀取模式,允許一個線程處於寫入模式並獨占鎖定狀態,同時還允許一個具有讀取權限的線程處於可升級的讀取模式,在此模式下線程無需放棄對資源的讀取權限即可升級為寫入模式。
注意 ReaderWriterLockSlim 類似於 ReaderWriterLock,只是簡化了遞歸、升級和降級鎖定狀態的規則。 ReaderWriterLockSlim 可避免多種潛在的死鎖情況。 此外,ReaderWriterLockSlim 的性能明顯優於 ReaderWriterLock。 建議在所有新的開發工作中使用 ReaderWriterLockSlim。
以上引用自MSDN
ps:該類在.NET3.5中提供,如需要在2.0中使用請換ReaderWriterLock,用法差不多改了寫方法名,MSDN中說ReaderWriterLockSlim性能比較高
主要屬性,方法
屬性:
IsReadLockHeld 獲取一個值,該值指示當前線程是否已進入讀取模式的鎖定狀態。
IsWriteLockHeld 獲取一個值,該值指示當前線程是否已進入寫入模式的鎖定狀態。
方法:
EnterReadLock 嘗試進入讀取模式鎖定狀態。
ExitReadLock 減少讀取模式的遞歸計數,並在生成的計數為 0(零)時退出讀取模式。
EnterWriteLock 嘗試進入寫入模式鎖定狀態。
ExitWriteLock 減少寫入模式的遞歸計數,並在生成的計數為 0(零)時退出寫入模式。
當然還有其他很多方法,比如EnterUpgradeableReadLock進入可以升級到寫入模式的讀取模式..
不過我需要封裝的對象相對來說較為簡單,所以不需要用這些額外的方法和屬性,有興趣的可以自己去研究下
應用
來對比一個老式的lock寫法
private object _Lock = new object(); private void Read() { lock (_Lock) { //具體方法實現 } } private void Write() { lock (_Lock) { //具體方法實現 } }
讀寫鎖分離
private ReaderWriterLockSlim _LockSlim = new ReaderWriterLockSlim(); private void Read() { try { _LockSlim.EnterReadLock(); //具體方法實現 } finally { _LockSlim.ExitReadLock(); } } private void Write() { try { _LockSlim.EnterWriteLock(); //具體方法實現 } finally { _LockSlim.ExitWriteLock(); } }
看上下2種寫法:
從性能的角度來說,肯定是讀寫鎖分離更好了,特別是大多數場合(讀取操作遠遠多余寫入操作)
從可讀性和代碼美觀度來說,就是上面的lock要簡潔的多了,維護起來也更清晰
所以我希望重新封裝ReaderWriterLockSlim,當然我第一想到的就是using了,利用using語法糖的特性封裝一個新的對象
封裝
Code平台: UsingLock
由於是利用的using的語法,所以我直接取名叫UsingLock,簡單好記

using System; using System.Threading; namespace blqw { /// <summary> 使用using代替lock操作的對象,可指定寫入和讀取鎖定模式 /// </summary> public class UsingLock<T> { #region 內部類 /// <summary> 利用IDisposable的using語法糖方便的釋放鎖定操作 /// <para>內部類</para> /// </summary> private struct Lock : IDisposable { /// <summary> 讀寫鎖對象 /// </summary> private ReaderWriterLockSlim _Lock; /// <summary> 是否為寫入模式 /// </summary> private bool _IsWrite; /// <summary> 利用IDisposable的using語法糖方便的釋放鎖定操作 /// <para>構造函數</para> /// </summary> /// <param name="rwl">讀寫鎖</param> /// <param name="isWrite">寫入模式為true,讀取模式為false</param> public Lock(ReaderWriterLockSlim rwl, bool isWrite) { _Lock = rwl; _IsWrite = isWrite; } /// <summary> 釋放對象時退出指定鎖定模式 /// </summary> public void Dispose() { if (_IsWrite) { if (_Lock.IsWriteLockHeld) { _Lock.ExitWriteLock(); } } else { if (_Lock.IsReadLockHeld) { _Lock.ExitReadLock(); } } } } /// <summary> 空的可釋放對象,免去了調用時需要判斷是否為null的問題 /// <para>內部類</para> /// </summary> private class Disposable : IDisposable { /// <summary> 空的可釋放對象 /// </summary> public static readonly Disposable Empty = new Disposable(); /// <summary> 空的釋放方法 /// </summary> public void Dispose() { } } #endregion /// <summary> 讀寫鎖 /// </summary> private ReaderWriterLockSlim _LockSlim = new ReaderWriterLockSlim(); /// <summary> 保存數據 /// </summary> private T _Data; /// <summary> 使用using代替lock操作的對象,可指定寫入和讀取鎖定模式 /// <para>構造函數</para> /// </summary> public UsingLock() { Enabled = true; } /// <summary> 使用using代替lock操作的對象,可指定寫入和讀取鎖定模式 /// <para>構造函數</para> /// <param name="data">為Data屬性設置初始值</param> public UsingLock(T data) { Enabled = true; _Data = data; } /// <summary> 獲取或設置當前對象中保存數據的值 /// </summary> /// <exception cref="MemberAccessException">獲取數據時未進入讀取或寫入鎖定模式</exception> /// <exception cref="MemberAccessException">設置數據時未進入寫入鎖定模式</exception> public T Data { get { if (_LockSlim.IsReadLockHeld || _LockSlim.IsWriteLockHeld) { return _Data; } throw new MemberAccessException("請先進入讀取或寫入鎖定模式再進行操作"); } set { if (_LockSlim.IsWriteLockHeld == false) { throw new MemberAccessException("只有寫入模式中才能改變Data的值"); } _Data = value; } } /// <summary> 是否啟用,當該值為false時,Read()和Write()方法將返回 Disposable.Empty /// </summary> public bool Enabled { get; set; } /// <summary> 進入讀取鎖定模式,該模式下允許多個讀操作同時進行 /// <para>退出讀鎖請將返回對象釋放,建議使用using語塊</para> /// <para>Enabled為false時,返回Disposable.Empty;</para> /// <para>在讀取或寫入鎖定模式下重復執行,返回Disposable.Empty;</para> /// </summary> public IDisposable Read() { if (Enabled == false || _LockSlim.IsReadLockHeld || _LockSlim.IsWriteLockHeld) { return Disposable.Empty; } else { _LockSlim.EnterReadLock(); return new Lock(_LockSlim, false); } } /// <summary> 進入寫入鎖定模式,該模式下只允許同時執行一個讀操作 /// <para>退出讀鎖請將返回對象釋放,建議使用using語塊</para> /// <para>Enabled為false時,返回Disposable.Empty;</para> /// <para>在寫入鎖定模式下重復執行,返回Disposable.Empty;</para> /// </summary> /// <exception cref="NotImplementedException">讀取模式下不能進入寫入鎖定狀態</exception> public IDisposable Write() { if (Enabled == false || _LockSlim.IsWriteLockHeld) { return Disposable.Empty; } else if (_LockSlim.IsReadLockHeld) { throw new NotImplementedException("讀取模式下不能進入寫入鎖定狀態"); } else { _LockSlim.EnterWriteLock(); return new Lock(_LockSlim, true); } } } }
方法:
Read() 進入讀取鎖定模式
Write() 進入寫入鎖定模式
另外我還加入2個額外的屬性
Data UsingLock中可以保存一個數據,由當前線程中的環境判斷是否可以讀取或設置該對象
Enabled 是否啟用當前組件..這個有妙用,下面介紹
使用場合

/// <summary> 假設有這樣一個隊列系統 /// </summary> class MyQueue:IEnumerable<string> { List<string> _List; UsingLock<object> _Lock; public MyQueue(IEnumerable<string> strings) { _List = new List<string>(strings); _Lock = new UsingLock<object>(); } /// <summary> 獲取第一個元素.並且從集合中刪除 /// </summary> public string LootFirst() { using (_Lock.Write()) { if (_List.Count == 0) { _Lock.Enabled = false; return null; } var s = _List[0]; _List.RemoveAt(0); return s; } } public int Count { get { return _List.Count; } } /// <summary> 枚舉當前集合的元素 /// </summary> public IEnumerator<string> GetEnumerator() { using (_Lock.Read()) { foreach (var item in _List) { yield return item; } } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } }
我這里假設了一個隊列系統,把最容易出現問題的修改集合和枚舉集合2個操作公開出來,方便在多線程中測試效果
以下為測試代碼:
static void Main(string[] args) { //建立一個字符串集合,總數為1000 List<string> list = new List<string>(1000); for (int i = 0; i < list.Capacity; i++) { list.Add("字符串:" + i); } MyQueue mq = new MyQueue(list); //保存最后一個值,等下用於做比較 string last = list[list.Count - 1]; //開啟1000個線程,同時執行LootFirst方法,並打印出結果 for (int i = 0; i < list.Capacity; i++) { ThreadPool.QueueUserWorkItem(o => { Console.WriteLine(mq.LootFirst()); }); } //在主線程中不停調用mq的遍歷方法,這樣的操作是很容易出現線程爭搶資源的,如果沒有鎖定訪問機制,就會出現異常 while (mq.Count > 0) { foreach (var item in mq) { //如果最后一個值還在,就輸出 "還在" if (item == last) { Console.WriteLine("還在"); } } } }
測試結果
Release模式下也是很輕松就跑完了,證明訪問的同步控制部分是可以正常工作的
使用詳細說明
語法上是不是跟lock比較類似了?Enabled屬性的作用在這里就可見一斑了
這部分比較簡單,就不多說了.....
對比無lock
當然寫完可以用,還需要和原始的方式比較一下,不然不知道優劣
對比無lock模式
將using代碼注釋,果然出現了異常
對比原始單一lock
對比原始lock模式,這次需要加上時間
UsingLock VS 單一lock
--------