[.NET] : Thread Safe Collection
前言 :
最近为了多执行绪程序分享数据集合,搞的焦头烂额。
主要的问题点卡在,
当有一条执行绪使用 foreach列举数据集合的时候,另外一条执行绪去变更数据集合。
这时候会发生Exception,通知说在列举的同时数据集合被变更。
当下最先想到的解决方案是,使用lock在读写数据集合的时候做锁定。
这样的确可以解决问题,
但是因为不论读写都先lock,这样会降低程序执行的效能。
并且这样的写法,要求使用数据集合的程序代码必须要记得做lock动作,不然会发生错误。
上网搜寻到这篇「再谈程序多任务(III)─执行绪安全与数据集合」,刚好解决了我遇到的问题点。:D
整篇文章主要的思路就是,
使用 ReaderWriterLockSlim,建立IEnumerable、IEnumerator接口封装lock动作。
在读取、列举数据集合的时候,做只读的Lock。
在写入数据集合的时候,做写入的Lock并且不允许只读的的Lock。
依照这样的方式建立起来的数据集合,
在使用外观上因为是建立IEnumerable、IEnumerator接口封装lock动作,使用数据集合的程序代码可以不用注意lock动作。
并且读写两种lock分离,可以在大量取数据,少量写数据的情景下,达成最大的效率。
本篇文章依照「再谈程序多任务(III)─执行绪安全与数据集合」的思路,整理出可扩展的Thread safe Collection。
留个纪录,也希望能帮助到有同样问题的开发人员。 :D
程序代码 :
ThreadSafeReaderWriterLock :
首先建立 ThreadSafeReaderWriterLock用来完善 ReaderWriterLock的Dipose功能。
参考数据 : 「再谈程序多任务(I)──升级版的数据锁定和等待机制」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace CLK.Threading { public class ThreadSafeReaderWriterLock : IDisposable { // Fields private readonly ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim(); private readonly CountdownEvent _countdownEvent = new CountdownEvent(1); // Constructor public ThreadSafeReaderWriterLock() { } public void Dispose() { _countdownEvent.Signal(); _countdownEvent.Wait(); _countdownEvent.Dispose(); _readerWriterLock.Dispose(); } // Methods public void EnterReadLock() { _countdownEvent.AddCount(); _readerWriterLock.EnterReadLock(); } public void ExitReadLock() { _readerWriterLock.ExitReadLock(); _countdownEvent.Signal(); } public void EnterWriteLock() { _countdownEvent.AddCount(); _readerWriterLock.EnterWriteLock(); } public void ExitWriteLock() { _readerWriterLock.ExitWriteLock(); _countdownEvent.Signal(); } } } |
ThreadSafeEnumerator<T> :
接着建立 ThreadSafeEnumerator<T>,用以处理使用foeach做列举的lock动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using CLK.Threading; namespace CLK.Collections.Concurrent { public sealed class ThreadSafeEnumerator<T> : IEnumerator<T>, IDisposable { // Fields private readonly ThreadSafeReaderWriterLock _readerWriterLock = null ; private readonly IEnumerator<T> _component = null ; private readonly object _syncRoot = new object (); private bool _disposed = false ; // Constructor public ThreadSafeEnumerator(Func getEnumeratorDelegate, ThreadSafeReaderWriterLock readerWriterLock) { #region Require if (getEnumeratorDelegate == null ) throw new ArgumentNullException(); if (readerWriterLock == null ) throw new ArgumentNullException(); #endregion // ReaderWriterLock _readerWriterLock = readerWriterLock; _readerWriterLock.EnterReadLock(); // Component _component = getEnumeratorDelegate(); } public void Dispose() { // Require lock (_syncRoot) { if (_disposed == true ) return ; _disposed = true ; } // Component _component.Dispose(); // ReaderWriterLock _readerWriterLock.ExitReadLock(); } // Properties public T Current { get { return _component.Current; } } object System.Collections.IEnumerator.Current { get { return this .Current; } } // Methods public bool MoveNext() { return _component.MoveNext(); } public void Reset() { _component.Reset(); } } } |
ThreadSafeEnumerable<T> :
再来建立 ThreadSafeEnumerable<T>,来处理读写数据 lock动作。并且他也是后续扩展数据集合的基础对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using CLK.Threading; namespace CLK.Collections.Concurrent { public class ThreadSafeEnumerable<T> : IEnumerable<T>, IDisposable { // Fields private readonly ThreadSafeReaderWriterLock _readerWriterLock = null ; private readonly IEnumerable<T> _component = null ; private readonly object _syncRoot = new object (); private bool _disposed = false ; // Constructor protected ThreadSafeEnumerable(IEnumerable<T> component) { #region Require if (component == null ) throw new ArgumentNullException(); #endregion // ReaderWriterLock _readerWriterLock = new ThreadSafeReaderWriterLock(); // Component _component = component; } public void Dispose() { // Require lock (_syncRoot) { if (_disposed == true ) return ; _disposed = true ; } // ReaderWriterLock _readerWriterLock.Dispose(); // Component if (_component is IDisposable) { ((IDisposable)_component).Dispose(); } } // Methods protected void EnterReadLock() { _readerWriterLock.EnterReadLock(); } protected void ExitReadLock() { _readerWriterLock.ExitReadLock(); } protected void EnterWriteLock() { _readerWriterLock.EnterWriteLock(); } protected void ExitWriteLock() { _readerWriterLock.ExitWriteLock(); } public IEnumerator<T> GetEnumerator() { return new ThreadSafeEnumerator<T>(_component.GetEnumerator, _readerWriterLock); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this .GetEnumerator(); } } } |
ThreadSafeCollection<T> :
这是第一个扩展的数据集合,是以ThreadSafeEnumerable<T>为基础,并且实作ICollection<T>的接口功能。
特别要注意的是,读写数据集合式采用不同的Lock动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Threading; namespace CLK.Collections.Concurrent { public class ThreadSafeCollection<T> : ThreadSafeEnumerable<T>, ICollection<T> { // Fields private readonly ICollection<T> _component = null ; // Constructor public ThreadSafeCollection() : this ( new List<T>()) { } protected ThreadSafeCollection(ICollection<T> component) : base (component) { #region Require if (component == null ) throw new ArgumentNullException(); #endregion // Component _component = component; } // Properties public int Count { get { try { this .EnterReadLock(); return _component.Count; } finally { this .ExitReadLock(); } } } public bool IsReadOnly { get { try { this .EnterReadLock(); return _component.IsReadOnly; } finally { this .ExitReadLock(); } } } // Methods public void Add(T item) { try { this .EnterWriteLock(); _component.Add(item); } finally { this .ExitWriteLock(); } } public bool Remove(T item) { try { this .EnterWriteLock(); return _component.Remove(item); } finally { this .ExitWriteLock(); } } public void Clear() { try { this .EnterWriteLock(); _component.Clear(); } finally { this .ExitWriteLock(); } } public bool Contains(T item) { try { this .EnterReadLock(); return _component.Contains(item); } finally { this .ExitReadLock(); } } public void CopyTo(T[] array, int arrayIndex) { try { this .EnterReadLock(); _component.CopyTo(array, arrayIndex); } finally { this .ExitReadLock(); } } } } |
ThreadSafeList<T> :
最后再扩展一个数据集合ThreadSafeList<T> ,是以ThreadSafeCollection<T>为基础,并且实作IList<T>的接口功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CLK.Collections.Concurrent { public class ThreadSafeList<T> : ThreadSafeCollection<T>, IList<T> { // Fields private readonly IList<T> _component = null ; // Constructor public ThreadSafeList() : this ( new List<T>()) { } protected ThreadSafeList(IList<T> component) : base (component) { #region Require if (component == null ) throw new ArgumentNullException(); #endregion // Component _component = component; } // Properties public T this [ int index] { get { try { this .EnterReadLock(); return _component[index]; } finally { this .ExitReadLock(); } } set { try { this .EnterWriteLock(); _component[index] = value; } finally { this .ExitWriteLock(); } } } // Methods public void Insert( int index, T item) { try { this .EnterWriteLock(); _component.Insert(index, item); } finally { this .ExitWriteLock(); } } public void RemoveAt( int index) { try { this .EnterWriteLock(); _component.RemoveAt(index); } finally { this .ExitWriteLock(); } } public int IndexOf(T item) { try { this .EnterReadLock(); return _component.IndexOf(item); } finally { this .ExitReadLock(); } } } } |
后记 :
依照上面扩展数据集合的方式,可以扩展出例如 IDictionary等等的各种Thread Safe Collection。 :D
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?