没错,这次章节没有女仆。
前情回顾
我们的最初的需求是建立一个拉模式下用户暂存的顺序信息池
还是这张工作模式图 我们可以把这个需求设计为
Clear:清除所有内容
GetEnumerator :实现枚举器,新向旧方向的顺序枚举,这样一旦到达上次读取的时间就可以中断枚举。
RecycleFromButtom:从旧向前进行搜索 把满足条件的扔到GC
StackOn :把一个新信息放在堆栈的顶部
这就好像是一个旧报纸回收传送带,一群人在焚烧之前看看还有没有什么值得保存的信息 排个照,存个档,没有用的就直接扔进焚化炉。
实现设计
根据上一章的研究结果 我们需要一个能够在写的同时 能够完全无锁并发访问的顺序数据池
看起来基于 Array的任何数据结构都不太适合并发读。
这时候我们把目光转向链表结构。
链表在实现聊天室时得天独厚
姑且我们为垃圾列表建立这样一个链表节点
public void StackOn( IRecycleNode < T > newOne) { _lock.EnterWriteLock (); newOne.Lower = newOne.Higher = null ; if (_Top != null ) { _Top.Higher = newOne; newOne.Lower = _Top; _Top = newOne; } else { _Top = newOne ; _Bottom = _Top; } newOne.TimeStamp = DateTime.Now; _lock.ExitWriteLock(); }
我们以此作为基础继续讨论。
关于枚举器并发访问
相比循环队列中我们MoveNext的时候需要的 _size, _head, _index 和array中元素 这几个变量随时可能产生的并发冲突
public bool MoveNext() { if ( this ._version != this ._q._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } if ( this ._index == - 2 ) { return false ; } this ._index ++ ; if ( this ._index == this ._q._size) { this ._index = - 2 ; this ._currentElement = default (T); return false ; } this ._currentElement = this ._q.GetElement( this ._index); return true ; }
Queue:
internal T GetElement(int i) { return this ._array[(this ._head + i) % this ._array.Length]; }
在MoveNext的时候 我们的RecycleList访问的是一个不会脏的引用 :Lower
public bool MoveNext() { if ( ! startedFlag) { startedFlag = true ; return (_beginNode != null ); }; var cl = _currentNode.Lower; if (cl != null ) { _currentNode = cl; return true ; } else return false ; }
不冲突 就是不冲突~
关于并发回收
链表回收相当的简单, node. Lower=null;
一旦node的lower设置为 null 那么下面的所有节点就脱离了GCRoot 就可以被回收了
如果别的线程正在其下的_Bottom检查回收, 由于 Higher的联系仍然没断,向上的枚举仍然可以进行
public void RecycleFromButtom(Func < IRecycleNode < T > , bool > RecycleCondition) { var bn = _Bottom; // if (_Bottom == null) _Bottom = ; var bh = bn; if (bh != null ) bh = bh.Higher; while (bn != null & bh != null ) { if (RecycleCondition(bn)) { bh.Lower = null ; } _Bottom = bh; bn = _Bottom; bh = bn.Higher; } }
_Bottom 决定了GC能够回收的最后一个节点。 它不需要安全。 就算它被指定为 实际底部下面的其他节点 也仅仅使一两个节点苟延残喘一个周期。
唯一的迷你锁:StackOn
理论上的完全无锁 被破坏在写入这一关。
由于在链表头部增加节点并不是原子操作,所以这里必须增加一个写入锁
这个锁非常非常的小 :
public void StackOn( IRecycleNode < T > newOne) { _lock.EnterWriteLock (); newOne.Lower = newOne.Higher = null ; if (_Top != null ) { _Top.Higher = newOne; newOne.Lower = _Top; _Top = newOne; } else { _Top = newOne ; _Bottom = _Top; } newOne.TimeStamp = DateTime.Now; _lock.ExitWriteLock(); }
由于 对_top的修改是原子性的
_Top.Higher = newOne;
newOne.Lower = _Top;
_Top = newOne;
在创建Enumertor的时候 完全可以脏读————没有人在意自己读取的是最上面一条信息 还是第二个信息 他们只关心是否能读下去
只要我们的ChatMessage 实现 IRecycleNode,我们就可以把它放进这个池中了
Code namespace WayneGameSolution.Chat { public class ChatMessage : IChatMessage, IRecycleNode < IChatMessage > { public ChatMessage() { } #region IChatMessage Members public string ChannelID { get ; set ; } public string ChannelName { get ; set ; } public string ChaterFrom { get ; set ; } public string ChaterTo { get ; set ; } public MessageRenderType RenderType { get ; set ; } public string Text { get ; set ; } public DateTime TimeStamp { get ; set ; } #endregion #region ICloneable Members public object Clone() { return MemberwiseClone(); } #endregion #region IRecycleNode<IChatMessage> Members public IRecycleNode < IChatMessage > Lower { get ; set ; } public IRecycleNode < IChatMessage > Higher { get ; set ; } public IChatMessage Value { get { return this ; } set { throw new NotImplementedException(); } } #endregion } }
垃圾列表最终定稿
RecycleList using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.ComponentModel.Design; using System.Runtime.InteropServices; using System.Diagnostics; using System.Threading; using System.Security.Permissions; namespace System.Collections.Generic.Sync { public class RecycleList < T > : IEnumerable < T > { System.Threading.ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); IRecycleNode < T > _Bottom, _Top; // public IRecycleNode<T> Top // { // get // { // _lock.EnterReadLock(); // var t = _Top; // _lock.ExitReadLock(); // return t; // } // } public void Clear() { _lock.EnterWriteLock(); _Top = null ; _lock.ExitWriteLock(); } public void StackOn( IRecycleNode < T > newOne) { _lock.EnterWriteLock (); newOne.Lower = newOne.Higher = null ; if (_Top != null ) { _Top.Higher = newOne; newOne.Lower = _Top; _Top = newOne; } else { _Top = newOne ; _Bottom = _Top; } newOne.TimeStamp = DateTime.Now; _lock.ExitWriteLock(); } public void RecycleFromButtom(Func < IRecycleNode < T > , bool > RecycleCondition) { var bn = _Bottom; // if (_Bottom == null) _Bottom = ; var bh = bn; if (bh != null ) bh = bh.Higher; while (bn != null & bh != null ) { if (RecycleCondition(bn)) { bh.Lower = null ; } _Bottom = bh; bn = _Bottom; bh = bn.Higher; } } #region IEnumerable<T> Members public IEnumerator < T > GetEnumerator() { return new RecycleListEnumerator < T > (_Top); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return new RecycleListEnumerator < T > (_Top); } #endregion } public interface IRecycleNode < T > { IRecycleNode< T > Lower { get ; set ; } IRecycleNode < T > Higher { get ; set ; } T Value { get ; set ; } DateTime TimeStamp { get ; set ; } } public class RecycleListEnumerator < T > : IEnumerator < T > { IRecycleNode< T > _beginNode; IRecycleNode < T > _currentNode; public RecycleListEnumerator(IRecycleNode < T > beginNode) { _beginNode = _currentNode = beginNode; } #region IEnumerator<T> Members public T Current { get { return _currentNode.Value; } } #endregion #region IDisposable Members public void Dispose() { _beginNode = null ; _currentNode = null ; } #endregion #region IEnumerator Members object IEnumerator.Current { get { return Current; } } private bool startedFlag = false ; public bool MoveNext() { if ( ! startedFlag) { startedFlag = true ; return (_beginNode != null ); }; var cl = _currentNode.Lower; if (cl != null ) { _currentNode = cl; return true ; } else return false ; } public void Reset() { _currentNode = _beginNode; } #endregion } }
这里提供一个 MVC的测试聊天室 包括所有源代码
下载
大厅源代码正在googlecode 上用c#缓慢重建 有兴趣的可以下载
wgs-game-lobby.googlecode.com
连接:
游戏大厅 从基础开始(1)——最简单的关系,用户与房间
游戏大厅 从基础开始(2)——最基础的交流:聊天
游戏大厅 从基础开始(3)——最吸引眼球的部分 客户端与服务器的连接
游戏大厅 从基础开始(3.5)——最吸引眼球的部分 客户端与服务器的连接 的实现
游戏大厅 从基础开始(4)-通过L2X用配置文件反射组装程序(VB only)
游戏大厅 从基础开始(5)--绕回来细说聊天室(上)
游戏大厅 从基础开始(6)--绕回来细说聊天室(中)之女仆编年史1
游戏大厅 从基础开始(7)--绕回来细说聊天室(中间偏下)之女仆编年史2
游戏大厅 从基础开始(8)--绕回来细说聊天室(下)垃圾列表
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端