关于Dictionary中的对象如何实现过期清理?(类似于网站session功能的实现) 本帖最后由 hwbox 于 2010-07-14 15:51:46 编辑 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 你的线程数是根据客户端而定的吗?,那如果有1W个客户端,难道需要开1W个线程吗? 几乎是没有这样的架构WCF提供了三种并发模式,分别是:Single:最多允许每个实例上下文同时拥有一个对该实例上下文中的消息进行处理的线程。其他希望使用同一个实例上下文的线程必须一直阻塞,直到原始线程退出该实例上下文为止。Multiple:每个服务实例都可以拥有多个同时处理消息的线程。若要使用此并发模式,服务实现必须是线程安全的。Reentrant:每个服务实例一次只能处理一个消息,但可以接受可重入的操作调用。仅当服务通过 WCF 客户端对象提供服务 遍历dictionary的方法我用过了。在foreach中不能remove,我的第一选择还是希望能够remove的。所以我用keys.copyto到一个数组中,再遍历。这有一个问题,在我的机器上copy一个1000000的dictionary的时间是11ms,我估计在服务器上也好不到那去,这对于一个还有不停的读写操作的共享dictionary可以说是不能接受的。所以想问大家有什么好办法。 使用内存数据库吧......将并发交给数据库去完成, 数据在内存,速度也快;另外, 你的共享的dictionary只有一个, 多线程去操作,反而影响效率, 我还是建议你尽量减少线程, 你大量的线程处于空闲状态,为什么就不能用一个线程去解决呢? 内存数据库还不如dictionary快呢。在我的PC上dictionary每秒的多线程写可以达到几百万。这不算影响效率吧。另我的大量线程没有处于空闲。请别想架构和线程的事了,架构和线程的事我认为我搞的还是挺明白的。不明白的是怎么把大量的对象自动消毁。你回答的不是我想问的啊。 另外就是用一个线程去解决也请说一下,怎么解决。目前我有一个思路是用一个循环链表,每个node是一个时间点,比如我的session60秒过期,就设一个61位的链表,链表的每节点中再放一个dictionary,每0.5秒,触发一次检查线程。新的session产生后,先在全局dictionary中加一项,再在当前循环链表的当前节点上加一项。当session时间刷新时,就把全局dictionary中的session时间更改,再在循环链表中的原节点dictionary上remove,再在当前节点的dictionary上add。链表检查线程,检查时只检查循环链表的最后两个节点,remove其中的值并,remove全局dictionary中的值。但这种做法的讨厌之处在于,加锁很麻烦。此处,如果想控制链表的长度,就只有牺牲时间的精度,比如session的过期时间只有精确到10秒,那过期时间30分钟的链表也有180个节点了。我想我这个是相笨办法,我希望有高手能给出更好的建议。再次希望,别跑题。 已经是dictionary了,没必要移除,只要考虑定时修改就可以了 C#构建删除安全的迭代器class Program{ static void Main(string[] args) { SafeList<int> l = new SafeList<int>(); l.Add(1); l.Add(2); l.Add(3); foreach (var item in l.ItemList) { if (item == 2) l.Remove(item); } foreach (var item in l.ItemList) { Console.WriteLine(item); } }}class SafeItem<T>{ public T Value { get; set; } public bool IsRemoved { get; set; }}class SafeList<T>{ List<SafeItem<T>> itemList = new List<SafeItem<T>>(); public void Add(T value) { itemList.Add(new SafeItem<T> { Value = value }); } public bool Remove(T value) { var item = itemList.Find(i => i.Value.Equals(value)); if (item != null) { item.IsRemoved = true; return true; } else { return false; } } public IEnumerable<T> ItemList { get { itemList.RemoveAll(i => i.IsRemoved); foreach (var item in itemList) { if (item.IsRemoved) continue; yield return item.Value; } } }} 其实你用For也行...在For循环外定义循环次数 首先谢谢hyblusea不厌其烦的帮我想办法。您给的程序是3.5的,我们这儿用2.0我改了下。using System;using System.Collections;using System.Collections.Generic;using System.Text;using System.Diagnostics;namespace safelist{ class Program { private static int totalCount = 1000000; static void Main(string[] args) { SafeList<int> l = new SafeList<int>(); Stopwatch watch = new Stopwatch(); watch.Reset(); watch.Start(); for (int i=0; i < totalCount; i++) { l.Add(i); } watch.Stop(); Console.WriteLine(string.Format("list<T>添加{0}项耗时:{1}ms", totalCount,watch.ElapsedMilliseconds)); watch.Reset(); watch.Start(); for (int i = 0; i < totalCount; i=i+10000) { l.Find(i); } watch.Stop(); Console.WriteLine(string.Format("list<T>搜索{0}项耗时:{1}ms", totalCount/10000, watch.ElapsedMilliseconds)); //foreach (int item in l.ItemList) //{ // if (item == 2) // l.Remove(item); //} //foreach (int item in l.ItemList) //{ // Console.WriteLine(item); //} Console.Read(); } } class SafeItem<T> { private T _value; public T Value { get { return _value; } set { _value = value; } } private bool _isRemoved; public bool IsRemoved { get { return _isRemoved; } set { _isRemoved = value; } } } class SafeList<T> { List<SafeItem<T>> itemList = new List<SafeItem<T>>(); public void Add(T value) { SafeItem<T> o = new SafeItem<T>(); o.Value = value; itemList.Add(o); } public SafeItem<T> Find(T value) { return itemList.Find(delegate(SafeItem<T> o) { return o.Value.Equals(value); }); } public bool Remove(T value) { SafeItem<T> item = itemList.Find(delegate(SafeItem<T> o) { return o.Value.Equals(value); }); if (item != null) { item.IsRemoved = true; return true; } else { return false; } } public IEnumerable<T> ItemList { get { itemList.RemoveAll(delegate(SafeItem<T> o) { return o.IsRemoved; }); foreach (SafeItem<T> item in itemList) { if (item.IsRemoved) continue; yield return item.Value; } } } }}执行结果list<T>插入100万,126ms---没问题list<T>中检索100条,1643ms--完全不靠谱。list<T>的find和hash表是没法比啊。我没有做先remove再 foreach测试,因为remove中也用了find,速度一定是惨不忍睹。那个IEnumerable接口的实现也用了find速度也不用提了,所以这个解决方案我用不了,不过还是再次感谢了。 这是退尔求其次的办法,如果不允许一个用户在不同地点多次登录的话。我可以用userid替掉sessionid。这样dictionary不会因为不清理爆掉。因为userid是有限的。但我不太肯定我管理的那些客户端(对其它真正用户进行服务的服务)上是否允许用户多次登录。所以这只能是实在没办法的情况下的考虑。 昨天做了一个测试,我在dictionary的value上实现了一个链表,存储了上一节点,和下一个节点。并定义了一个全字典的锁。和链头,链尾标志。生成了add,move,check,del等方法。做了多线程测试。单开一个线程每0.5秒,从这个链表尾部向前收集过期节点。10个线程入线程池,每个插100000条,平均耗时约1500ms,总的完成时间约3秒,主要是线程启动的时间点差的时间不少。用单线程做check(检查sessionid是否存在,过期就删除,否则就移到链表头,并修改时间),100000个检查250ms。在之前的测试中一个dictionary的单线程Add 1000000 只要30几ms,和3秒比起来效率差了一百倍。考虑到链表add中,要对dictionary做一次trycetvalue,两次节点内容的修改,几个变量赋值,和一次dictionary的Add操作,所以本人大约给出链表add操作复杂性约dictionary的Add操作的10倍这么一个参考值。则线程调度和全局锁让add损失了约10倍。虽然,测试程序给出的效率已经满足了本人程序的需要,但事实上一个链表的add操作只要锁一个节点(链尾点)就够了。move要锁5个点。本人在解决move操作的同时锁5个点又要保证不互锁和不会锁到脏读问题上彻底想不出招了。所以用了全局的锁。有人用过非全局锁,只在节点上加锁的吗?是用一个什么思路呢?在一篇文章上看到了用hash值序,断定加锁序的。但感觉有可能在我找到上一个点之后,试图加锁之前被其它线程加上锁并被删除,造成脏读。所以这种按锁序加锁似乎只合适被锁者不会被删除的情况。大家还有什么好的想法吗? 以前看过陈广讲的C#内存回收机制, 可以把他的思路做为你程序实现的思路..系统管理的内存单元的数量级别应该不在你的数量级别之下...程序实现还需要跟据需求自已去完善..如果有资料可以邮件..索取[email protected] 好象和内存管理没有什么关系,内存管理是位图。我这是在dictionary上实现线程安全链表。 帮楼主顶一下...如果是3.0框架, 可以试一试 SortedDictionary..貌似在处理大数据量时, 速度会比Dictionary快一些 我会选择用cache [code]//以下方法可以写在HttpModule的OnPreRequestHandlerExecute事件中 public void Init(HttpApplication context) { context.PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute); }//你可以设sessionTimeout=30 HttpContext.Current.Cache.Insert(key, value, null, DateTime.MaxValue, sessionTimeout, CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(CacheItemRemovedCallbackMethod));private void CacheItemRemovedCallbackMethod(string key, object value, CacheItemRemovedReason reason) { //当cache过期 if (reason == CacheItemRemovedReason.Expired) { //处理你的dictionary } } 如果myclass时间明确的话,我会考虑用队列.. 写个HttpModulepublic void Init(HttpApplication context){ context.PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute);} private void OnPreRequestHandlerExecute(object sender, EventArgs e) { // Ensure have a HttpContext if (HttpContext.Current == null) { return; }//key,value根据需要定义 HttpContext.Current.Cache.Insert(key, value, null, DateTime.MaxValue, new TimeSpan(30), CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(CacheItemRemovedCallbackMethod)); } private void CacheItemRemovedCallbackMethod(string key, object value, CacheItemRemovedReason reason) { if (reason == CacheItemRemovedReason.Expired) { //处理dictionary逻辑 } } 看了下,在这个项目里用不到,基本上和Dictionary差不多,非托管的代码,IO还多了一道手续。也不知道是不是线程安全的,链表实现同样要自己进行,如果不用链表,还是解决不了过期检查的问题。可能在其它项目中有用,感觉还是不错的。cache 用时间依赖来解决过期确实是个好想法这样就不用做链表了。有空我测一下效率(我怀疑cache的过期 是用事件实现的,这样就依赖于系统的线程池有可能效率上还不如我的Dictionary链表),要是比Dictionary加锁做链表快,可以改用它。队列的随机查询... 测试报告:根据yuxuanji的提议使用Cache类测试以下代码,在控制台程序中执行近100遍,仅成功1次(居然真能有一次成功运行,我都怀疑我眼花了!!!)。翻书发现cache类的说明为 “实现用于web应用程序的缓存,无法继承此类。”。估计此类严重依赖于asp.net中的线程调度机制。无法正常使用在C/S程序中。此建议只能pass。 Cache theCache = new Cache(); AggregateCacheDependency agg = new AggregateCacheDependency(); object j = new object(); j = (object)"1"; theCache.Insert("1", j, null, DateTime.Now.AddSeconds(30), new TimeSpan(30000000)); 各位高手请帮忙再想想。要是没有什么更好的点子,我就用我的Dictionary链表了。 foreach 不能 remove 可以设置值的 你可以设置他的value 为 false 或者什么 暂时停止他的功能并且使用 List<string> 来记录所有设置false的键值再遍历List 删除所有包含的 Dictionary 键虽然效率低了点 可能要进行2次循环 但这个方法也是最浅显易懂的 但是我的程序是要考虑线程安全的。你的提议做法中在dictionary中foreach设置value中的标志值的时候,我必须对全字典加锁,并生成一个新的值为flase的list。保持对dictionary中的锁并再次遍历list,得到值,remov不要的节点。在这个加锁期内做了一次dictionary遍历,一次list遍历。对n个节点进行remove操作。感觉可能会让dictionary失去响应其它线程的时间过长。 确实,,因为字典 本身就是属于无序的排列,,如果按照正常的来循环 从 0 到 count-1 这样走只要remove了 然后在把 i-- 这样也行 但是foreach 好像控制不了 这个迭代的次序,,无法让他回滚到上一个元素 所以他考虑到这个而保护了字典不让删除吧,,,,这个我也不是很明白,,,但按逻辑来看 应该是这样,所以 如果你真的要控制他的话 建议你使用XML 用xPath去定位也比用 Dictionary 这样让你头疼好,,,不过既然你是网络程序 你也要考虑 XML 不能同时操作的问题。可以使用缓存数据 然后用某一个线程统一插入当然这只是建议,,最终决定还要看你自己 还有个时间效率的问题,我的想法是要求支持最小每秒10万以上的读写操作。所以不是以hashtable原理实现的对象我基本就不考虑了。 其实 xPath 只要你想 他就是hashtable原理,,,他就是一个定位的 你直接定位到他的元素,,把他的元素名作为键值是一摸一样的但是每秒10万次读写,,如果数据量小可以,,,百万级 难……其实还有个办法,,但是这样的话 可能冗余更大,,好像不如List方法每次foreac 找到那个元素以后 使用break 跳出来 然后删除这个key 并且把他扔到递归里 让递归再次去跑,,直到跑完停止递归 我觉得lz还是简单点处理比较好,当Dictionary数量超过一定值清空或者清理就行 这个我要没记错的话,好象递归层数是有限制的啊,一般栈2k的好象只支持2048层递归,以你的这办法一次删除不到3000个程序就over了。 楼主 看一下这篇文章 也许会给你带来点灵感!http://www.cnblogs.com/moozi/archive/2010/05/22/1741610.html 看了下,这个类在处理过期时间是,首先来了个遍历判断(虽然是用linq但实质上还是遍历)拷出来一个符合过期标准list,然后锁字典进行移除。我上次说过这个遍历是11ms左右(加上判断不知道多长时间),大约还可以接受,不过每秒都做应该是不行的。我看这个类是做了个30分钟清理,平时过期的session就存着。这个做法和我的链表的区别是,链表是按的时间关系是放好的(这消耗了节点出入字典的时间),但判断过期时,只要锁字典从尾部一个节点一个节点删除就行了。另外,这个类在get的时候好象没有考虑到session时间的更新。个人感觉有一定参考性,不过效率上和现有的方案比,没有大的提高。 其实简单的办法未必比复杂的效果差,用线程什么的没必要,也不能完全达到你的要求比如这样 public static Dictionary<string, Myclass> A = new Dictionary<string, Myclass>(); public static Dictionary<string, Myclass> B = new Dictionary<string, Myclass>(); public static DateTime LastSwtich = DateTime.Now; public static TimeSpan TimeOut = new TimeSpan(0, 0, 30); public static int MaxCapacity = 1000000; public static Myclass Get(string userid) { if (A.ContainsKey(userid)) { Myclass m = A[userid]; if (DateTime.Now - m.thelogintime > TimeOut) { A.Remove(userid); return null; } else { m.thelogintime = DateTime.Now; return m; } } else if (B.ContainsKey(userid)) { Myclass m = B[userid]; if (DateTime.Now - m.thelogintime > TimeOut) { return null; } else { m.thelogintime = DateTime.Now; A[userid] = m; return m; } } else return null; } public static void Set(string userid, Myclass user) { if (A.Count >= MaxCapacity && DateTime.Now - LastSwtich > TimeOut) { Dictionary<string, Myclass> temp = A; A = B; B = temp; A.Clear(); } user.thelogintime = DateTime.Now; A[userid] = user; }当然多线程情况下还要考虑同步,只是个意思,lz参考下 上面代码A.Clear();后漏了一个LastSwtich = DateTime.Now;补上 Dictionary<string, Myclass> temp = A; A = B; B = temp; A.Clear();不好意思,我没看明白您的程序想做的是干什么,特别是这一段。你定义了一个 temp 指向了 A 指向的字典对象(DA)。又重新定义A指向B指向的对象(DB),又让B指向temp指向的字典对象(DA),也就是交换了一下AB两个字典,然后把A清除了。可这有什么意义呢?每次A都是空的,只加一条,把含这一条的字典交给B。B里永远只有一条啊?搞不明白您的意思。 if (A.Count >= MaxCapacity && DateTime.Now - LastSwtich > TimeOut)这个条件没看到吗?不是很难理解吧 不清楚LZ为什么要用Dictionary做链表!不是有现成的LinkedList么?我看LZ的应用,像是做缓存,过期后清理,并且,命中之后,这个过期时间是可以被修改的。LZ使用链表大概是希望每次清理的时候不用全部遍历,只要遍历一部分做清理就可以。如果是这样的话,我觉得可以从两个思路来入手。首先需要做的是,把时间按照一定的单位粒度离散化。1、使用SortedDictionary,或自己实现的优先队列来做。2、直接开一个数组,比如粒度是5秒,开10000长的数组就可以管10多个小时,一般够用,每5秒处理1次,只处理数组中的一个元素即可。 看明白了啊,当a的总数超过100万了,而且距离上次清理超出了你设定的时限了,此时,你把A和B交换了一下,清了。可我还是没搞明白你这么做是在做什么吧。我来说一下我的理解啊,你的get是先在A里找,A里没有就在B里找,B里就返回的同时在A里也建一个。你的set是在每次添加或更新前都判断一下,都判断一下当a的总数超过100万了,而且距离上次清理超出了你设定的时限了,此时,你把A和B交换了一下,清了。可这有什么用啊,我不明白,请您详细解释一下。以过期时间三十分钟为例,我第一分钟放的和第二十九分钟放的,你这个程序怎么判断呢。请仔细看一下我的贴子,以及后面的补充。还有此问题在于解决多线程的情况,单线程的我就不考虑了,谢谢。 LinkedList无法直接命中。SortedDictionary 无法用value排序,而key值是不能变的。按时间间隔离散化的方法我想过,用循环队列(用字典加头尾指针的方法实现的),value中再加字典的方法。缺点是当时间粒度小和过期时间大的时候,这个两级dictionary结构不好,并且效率不高。一个读操作时要把一个节点在两个字典间操作。 是这样的,把A称为主缓存,B称为候补缓存吧。查找时在A中查找,如果存在并且不过期,那么更新时间戳并返回结果就行。(我的理解你的要求应该是这样)如果存在并已过期,那么在A中删除该条记录并返回null。如果不存在,那么在B中再查找一次,如果存在并且不过期,那么更新时间戳,将该条记录添加到A中并返回结果。如果存在并已过期,那么返回null。(B中也可以删除该条记录)如果A和B中都不存在,那么当然返回null。在添加记录的时候,首先检查主缓存A是否达到容量上限。如果未达到上限那就不用说了。如果A已达到上限,那么再检查一下候补缓存B是否已过期(B过期的话,意味着B里面的数据全都过期了,也就没有意义了)。如果2者都满足,那么把当前主缓存切换为候补缓存,再重新生成新的主缓存。可能这样写lz就不会有歧义了,其实是等价的 B = A; A = new Dictionary<string,Myclass>();多线程的话简单一点把整个方法加锁就行了。lz的思路是定期清理过期数据,其实在存、取数据时检查是否过期就可以了,不必非得实时清理过期数据。.Net自带的Cache应该也是这个做法。 如果读很频繁的话,建议LZ采用懒惰模式(并且不能使用SortedDictionary),不要每次读都在两个Dictionary中做修改(其实离散化后,直接用LZ所说的循环队列就可以),而是在清理的时候,再把这些元素,放到对应的位置。另外实在不用每个都开一个Dictionary,用List就可以。 我可能误解lz的thelogintime为时间戳了,重新改下,给MyClass加一个时间戳,这样lz应该就明白了 public static Dictionary<string, Myclass> A = new Dictionary<string, Myclass>(); public static Dictionary<string, Myclass> B = new Dictionary<string, Myclass>(); public static DateTime LastSwtich = DateTime.Now; public static TimeSpan TimeOut = new TimeSpan(0, 30, 0); public static int MaxCapacity = 1000000; public static Myclass Get(string userid) { if (A.ContainsKey(userid)) { Myclass m = A[userid]; if (DateTime.Now - m.TimeStamp > TimeOut) { A.Remove(userid); return null; } else { m.TimeStamp = DateTime.Now; return m; } } else if (B.ContainsKey(userid)) { Myclass m = B[userid]; if (DateTime.Now - m.TimeStamp > TimeOut) { return null; } else { m.TimeStamp = DateTime.Now; A[userid] = m; return m; } } else return null; } public static void Set(string userid, Myclass user) { if (A.Count > MaxCapacity && DateTime.Now - LastSwtich > TimeOut) { B = A; A = new Dictionary<string,Myclass>(); LastSwtich = DateTime.Now; } user.thelogintime = DateTime.Now; A[userid] = user; } } public class Myclass { public DateTime TimeStamp; public DateTime thelogintime; public bool loginflag = true; } 漏改了一处,Set方法中的user.thelogintime = DateTime.Now;应该是user.TimeStamp = DateTime.Now; list 的 find 效率低的惊人 不知LZ能否接受这样的方案,就是增加一个类似操作日志的队列,每次更新thelogintime都添加一个记录,然后单独开一个线程检查该队列即可。(缺点就是把线程瓶颈转移到新增的队列操作上了)如:public class myclass{ public string key; public DateTime thelogintime; public bool loginflag = true;}public class myclassLog{ public myclassLog(DateTime logtime, myclass tag) { Logtime = logtime; Tag = tag; } public DateTime Logtime { get; set; } public myclass Tag { get; set; }}public class ClassTest{ Dictionary<string, myclass> dic; Queue<myclassLog> queue; object objDicLock = new object(); object objQueueLock = new object(); public ClassTest() { dic = new Dictionary<string, myclass>(); queue = new Queue<myclassLog>(); } // --------------- 例: --------------- public void Add(myclass item) { lock (objDicLock) { dic[item.key] = item; setlog(item); } } public void Update(string key) { myclass item = dic[key]; if (item.loginflag) { lock (objQueueLock) { if (item.loginflag) { item.thelogintime = DateTime.Now; setlog(item); } } } } // ------------------------------------ public void setlog(myclass tag) { lock (objQueueLock) { queue.Enqueue(new myclassLog(tag.thelogintime, tag)); } } public void clear() { while (queue.Peek().Logtime <= DateTime.Now.AddSeconds(-30)) { myclassLog log; lock (objQueueLock) { log = queue.Dequeue(); } if (log.Logtime == log.Tag.thelogintime) { lock (log.Tag) { if (log.Logtime == log.Tag.thelogintime) { log.Tag.loginflag = false; lock (objDicLock) { dic.Remove(log.Tag.key); } } } } } }} 其实可以变向弄一下..把它进行一次封装,,提供写与读的接口当读时就判断它是否过期..过期就清了它和返回null这样就不必定时去处理..当然这样的话不能有大量访问一次就没人访问的数据每次访问来做清理..省了事 此外,如果不离散化的处理,那么必然要在session上放时间,则每个读操作都将变成写操作。 因为不用Find,所以用List就可以!举个具体一点的例子。如果时间的粒度为5秒,Session的过期时间为1小时,开一个长度为720的数组就够了,List<MyClass>[] myCatch = new List<MyClass>[720];每5秒钟清理1次,也就是清理一个List<MyClass>,遍历List<MyClass>,如果具体的MyClass已经过期,则从Dictionary中删除掉,如果未过期,把他放到myCatch 对应的位置即可,逻辑上很简单。代码也很简单,并且这个清理完全用不上多线程,单一线程管理即可。 为便于大家讨论,贴本人目前测试实现代码,为保证方便做命中测试把sessionid 改成用int的了 public class OPLSession { //当前登录的opl系统唯一用户标识 public string OPLUserID; //定义一些其它的内容 public string var1; public string var2; public string var3; } class OPLSessionLinkTableNode { //指定前一个结点的sessionid// private string _LeftNodeSessionID; public string LeftNodeSessionID { get { return _LeftNodeSessionID; } set { _LeftNodeSessionID = value; } } //指定后一个结点的sessionid private string _NextNodeSessionID; public string NextNodeSessionID { get { return _NextNodeSessionID; } set { _NextNodeSessionID = value; } } //当前session的生成时间 private DateTime _SessionTime = new DateTime(); public DateTime SessionTime { get { return _SessionTime; } set { _SessionTime = value; } } public OPLSession theSessioninfo = new OPLSession(); } class OPLSessionLinkTable { //session过期时间 private TimeSpan _theSessonTimeOut = new TimeSpan(0); //session过期时间指定到秒 public int TheSessonTimeOut { get { return (int)Math.Floor( _theSessonTimeOut.TotalSeconds ) ; } set { _theSessonTimeOut= new TimeSpan(value * 10000000); } } //链表的锁 private readonly object syncRoot = new object(); //链表结束的指针(最新的一个) private string _EndPoint = ""; public string EndPoint { get { return _EndPoint; } } //链表开始的指针(最旧的一个) private string _BeginPoint = ""; public string BeginPoint { get { return _BeginPoint; } } //链表的字典存储结构 第一个为sessionid private Dictionary<string, OPLSessionLinkTableNode> theOPLSessionDictionary = new Dictionary<string, OPLSessionLinkTableNode>(); internal Dictionary<string, OPLSessionLinkTableNode> TheOPLSessionDictionary { get { return theOPLSessionDictionary; } } /// <summary> /// 在当前单点登录字典链表中添加一个session /// </summary> /// <param name="inp_SessionInfo">要添加的session信息</param> /// <returns>sessionid</returns> public string Add(OPLSession inp_SessionInfo, int i) { OPLSessionLinkTableNode theAddNode = new OPLSessionLinkTableNode(); string temp_SessionID = System.Guid.NewGuid().ToString(); //链表加锁 lock (syncRoot) { if (_EndPoint != "") { OPLSessionLinkTableNode theEndNode; if (theOPLSessionDictionary.TryGetValue(_EndPoint, out theEndNode)) { theAddNode.LeftNodeSessionID = _EndPoint; theAddNode.NextNodeSessionID = ""; theAddNode.theSessioninfo = inp_SessionInfo; theAddNode.SessionTime = DateTime.Now; try { //theOPLSessionDictionary.Add(temp_SessionID, theAddNode); theOPLSessionDictionary.Add(i.ToString(), theAddNode); } catch { //不成功不做处理,但丢失一个添加点。 return ""; } //添加成功了 //_EndPoint = theEndNode.NextNodeSessionID = temp_SessionID; //return temp_SessionID; _EndPoint = theEndNode.NextNodeSessionID = i.ToString(); return i.ToString(); } else { //致命异常,无法找到最后点! return ""; } } else { if (_BeginPoint != "") { //致命异常,无法找到最后点,但有开始点,链表断了 return ""; } else { //既无开始点,也无结束点。插入的是第一个点 theAddNode.LeftNodeSessionID = ""; theAddNode.NextNodeSessionID = ""; theAddNode.theSessioninfo = inp_SessionInfo; theAddNode.SessionTime = DateTime.Now; try { //theOPLSessionDictionary.Add(temp_SessionID, theAddNode); theOPLSessionDictionary.Add(i.ToString(), theAddNode); } catch { //不成功不做处理,但丢失一个添加点。 return ""; } //添加成功了 //_EndPoint = _BeginPoint = temp_SessionID; //return temp_SessionID; _EndPoint = _BeginPoint = i.ToString(); return i.ToString(); } } } }} OPLSessionLinkTable还有两个方法一个里贴不下 /// <summary> /// 根据指定的SessionID查找,返回一个OPLSession /// </summary> /// <param name="inp_SessionID"></param> /// <returns></returns> public OPLSession CheckSession(string inp_SessionID) { OPLSessionLinkTableNode theMoveNode; OPLSessionLinkTableNode theLeftNode; OPLSessionLinkTableNode theNextNode; OPLSessionLinkTableNode theEndNode; //链表加锁 lock (syncRoot) { if (theOPLSessionDictionary.TryGetValue(inp_SessionID, out theMoveNode)) { DateTime theNow = DateTime.Now; if (theNow - theMoveNode.SessionTime < _theSessonTimeOut) { theMoveNode.SessionTime = DateTime.Now; if (theMoveNode.LeftNodeSessionID != "") { //有前一个点 if (theOPLSessionDictionary.TryGetValue(theMoveNode.LeftNodeSessionID, out theLeftNode)) { //有前一个点找到了 theLeftNode.NextNodeSessionID = theMoveNode.NextNodeSessionID; } else { //有前一个点没找到??? //链断了,致使错误 } } else { _BeginPoint = theMoveNode.NextNodeSessionID; } if (theMoveNode.NextNodeSessionID != "") { if (theOPLSessionDictionary.TryGetValue(theMoveNode.LeftNodeSessionID, out theNextNode)) { //有后一个点找到了 theNextNode.LeftNodeSessionID = theMoveNode.LeftNodeSessionID; } else { //有后一个点没找到??? //链断了,致使错误 } } else { _EndPoint = theMoveNode.LeftNodeSessionID; } theMoveNode.NextNodeSessionID = ""; theMoveNode.LeftNodeSessionID = _EndPoint; _EndPoint = inp_SessionID; return theMoveNode.theSessioninfo; } else { //超时了 if (theMoveNode.LeftNodeSessionID != "") { //有前一个点 if (theOPLSessionDictionary.TryGetValue(theMoveNode.LeftNodeSessionID, out theLeftNode)) { //有前一个点找到了 theLeftNode.NextNodeSessionID = theMoveNode.NextNodeSessionID; } else { //有前一个点没找到??? //链断了,致使错误 } } else { _BeginPoint = theMoveNode.NextNodeSessionID; } if (theMoveNode.NextNodeSessionID != "") { if (theOPLSessionDictionary.TryGetValue(theMoveNode.LeftNodeSessionID, out theNextNode)) { //有后一个点找到了 theNextNode.LeftNodeSessionID = theMoveNode.LeftNodeSessionID; } else { //有后一个点没找到??? //链断了,致使错误 } } else { _EndPoint = theMoveNode.LeftNodeSessionID; } try { theOPLSessionDictionary.Remove(inp_SessionID); } catch { //不成功不做处理,但丢失一个session。 return null; } return null; } } else { return null; } } } /// <summary> /// 在当前单点登录字典链表中删除一个session /// </summary> /// <param name="inp_SessionInfo">要删除的SessionID</param> /// <returns></returns> public bool Del(string inp_SessionID) { OPLSessionLinkTableNode theDelNode; OPLSessionLinkTableNode theLeftNode; OPLSessionLinkTableNode theNextNode; string temp_SessionID = System.Guid.NewGuid().ToString(); //链表加锁 lock (syncRoot) { if (theOPLSessionDictionary.TryGetValue(inp_SessionID, out theDelNode)) { if (theDelNode.LeftNodeSessionID == "") { //不存在左点,则本点的右点为开始点 _BeginPoint = theDelNode.NextNodeSessionID; } else { //存在左点 if (theOPLSessionDictionary.TryGetValue(theDelNode.LeftNodeSessionID, out theLeftNode)) { //找到了左点 theLeftNode.NextNodeSessionID = theDelNode.NextNodeSessionID; } else { //有左点但没找到,没找到左点 //产生致使错误 链表断了 } } if (theDelNode.NextNodeSessionID == "") { //不存在右点,则本点的左点为结束点 _EndPoint = theDelNode.LeftNodeSessionID; } else { //存在右点 if (theOPLSessionDictionary.TryGetValue(theDelNode.NextNodeSessionID, out theNextNode)) { //找到了右点 theNextNode.LeftNodeSessionID = theDelNode.LeftNodeSessionID; } else { //有右点但没找到,没找到右点 //产生致使错误 链表断了 } } try { theOPLSessionDictionary.Remove (temp_SessionID); } catch { //不成功不做处理,但产生一个脏点,不在链表内。 } //添加成功了 return true; } else { //未找到要删除的点 return false; } } } public int Count { get { return theOPLSessionDictionary.Count; } } LZ可能未能理解的是,我说的方法,相当于用2个Hash做索引,首先保留LZ的dictionary<string,myclass>,每次读的时候,根据string找到对应的myclass,修改里面的thelogintime。另外再开一个720的数组,List<MyClass>[],定时在List<MyClass>[]中找到需要清理释放的元素,在dictionary中进行删除,当然同时也在List<MyClass>[]中进行删除。 同步太困难了,List<MyClass>[]中遍历一个失效时间段时,一个dictionary<string,myclass>中的值可能被读取也就是说改了thelogintime。也就是说在list遍历时要锁list和dictionary这和直接锁dictionary没什么区别啊。 lz的意思是过期时间是从登录开始计算?跟一般想法不一样我还是那个建议,在get的时候检查是否过期,不要另开线程也不要遍历清理过期数据 我明白你的意思,不过我在这儿每秒清理的初衷是为了实现一个session onend的事件(不是事件,就是让session onend的时候能做点事,精确到一秒就行),所以才考虑定时每秒清理的。这点可能是我没说明白。 我觉得用不着锁呀,LZ说说担心出现的情况吧。什么时候需要锁dictionary或List?List一直由单一线程管理,自然用不着锁,dictionary如果因为锁的问题,会出现什么情况,LZ现在的方案也同样存在这个问题。 设有十个线程会同时读dictionary中的session当读session时要改dictionary中的时间。也要改list<>[]中的值。如果此时做清理操作...。所以肯定要加锁啊。不然可以删除掉刚发生了访问的session啊。 读的时候,不用改list<>[]中的值,list<>[]的值,只通过清理程序来改,LZ可能担心的是,比如清理时是需要循环的,某个元素已经被定为需要清理的,而恰好此时,用户又进行了访问,修改了thelogintime,把这个元素改为不用清理的,此时可能会出现异常。但LZ只需要在修改thelogintime的时候,判断一下,如果该项已经过期,需要另开一个session,而不是修改这个thelogintime 读的时候为什么不改list<>[]中的值呢。list<>[]不是以登录时间片划分的吗。设过期时间1分钟,划分60片。则在第一秒登录的session放在list<>[1]中。第十秒放在list<>[10]中,但当第一秒产生的session在第十五秒发生了一次读的时候,这个session必须要移到list<>[15]中啊,要不然这个list<>[]还有什么意义。 lz的thelogintime实际上就是最后访问时间了。不知道lz有没有明白我的意思,简单来说就是2个Dictionary A和B,假设过期时间是半小时的话。每过半个小时把B中的数据淘汰,A中的数据移至B,同时给A赋一个新的Dictionary。每次访问先检查A再检查B,如果在B中并且没过期的话要把该条记录移至A中。这样能明白了? 我没有看完,但是我也曾经想过这个问题,几种方法:1:不直接for each 后删除, 而是for each后经过比较 找到过期数据的key,用list保存,保存完毕后再从list中读取数据删除。2:不需要及时真的删除的话可以在取数据是判断是否过期,若过期就删除。但是还有部分数据很长时间不会被用到,这时候一分钟处理一次,可以建一个30个list,表示未来三十分钟需要删除的key,每次添加数据的时候判断过期时间,若过期时间是15分钟,则将这个key放入第15个list,时间到了就将该list保存的key的数据都删除。30分钟后过期的key放入一个list,在每分钟检查过期的时候同时检查30分钟过期的那个list 设过期时间1分钟,划分60片。则在第一秒登录的session放在list<>[1]中。第十秒放在list<>[10]中,但当第一秒产生的session在第十五秒发生了一次读的时候,这个session必须要移到list<>[15]中啊(这个时候不做任何操作,而是在清理到list<>[10]的时候,再把它移动到list<>[15])清理的时候list<>[]中的并不一定都是过期的,如果过期了从Dictionary中删除,不过期放到对应的list<>中。 没必要多个线程改一下myclass{int thelogintime; //登录的系统毫秒 System.Environment.TickCount bool loginflag { get{ return (thelogintime -System.Environment.TickCount) >=30*1000}}}没有必要更新它,比如设置5分钟循环一次集合,是否要超时的,超时的移除掉. 把你的dictionary单独进行封装,维护一个有序队列保存对象引用,在dictionary添加、删除和修改的同时在队列中维护其位置。定时从队列尾部去除过期的对象。 多线程能让你循环更快一点?? 客户访问的时候再去检查他需要的section 是否过时. ASP.Net里的Cache也是多线程访问的,但也没用什么多线程啊。lz你只是想要跟Cache一样的缓存对吧? 确切的说,我是要实现跟cache的功能相近,但对效率要求的优先级更高。 我也没说要求实现多线程的那部分。那是在使用这个类时实现的。我只是要求这个实现类是个线程安全的。说白了就是实现一下Cache,我认为Cache就是线程安全的。 drawstring封装 菜鸟求助, c# 实例化窗体 求一个.net整站源码带编辑器源码的那种 如何让ListView的某一栏如:columns[3],不能让用户拉动 C#实例化时的问题 怎么把.TXT文件里的数据导入到ORACLE数据库里啊,各位老大,急啊! 求代码如何把某个具体对象的公共属性和公共字段的类型和值取出 datagridview列不能获得焦点 怎樣解決在xp下編寫的程序在2k下運行時介面混亂的問題呢? 如何做到winform窗体不让多开! 大家好,熟悉GDI的朋友帮忙看看
{
static void Main(string[] args)
{
SafeList<int> l = new SafeList<int>();
l.Add(1);
l.Add(2);
l.Add(3); foreach (var item in l.ItemList)
{
if (item == 2)
l.Remove(item);
} foreach (var item in l.ItemList)
{
Console.WriteLine(item);
}
}}class SafeItem<T>
{
public T Value { get; set; }
public bool IsRemoved { get; set; }
}class SafeList<T>
{
List<SafeItem<T>> itemList = new List<SafeItem<T>>(); public void Add(T value)
{
itemList.Add(new SafeItem<T> { Value = value });
} public bool Remove(T value)
{
var item = itemList.Find(i => i.Value.Equals(value));
if (item != null)
{
item.IsRemoved = true;
return true;
}
else
{
return false;
}
} public IEnumerable<T> ItemList
{
get
{
itemList.RemoveAll(i => i.IsRemoved);
foreach (var item in itemList)
{
if (item.IsRemoved)
continue;
yield return item.Value;
}
}
}
} 其实你用For也行...在For循环外定义循环次数
首先谢谢hyblusea不厌其烦的帮我想办法。您给的程序是3.5的,我们这儿用2.0我改了下。using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;namespace safelist
{
class Program
{
private static int totalCount = 1000000;
static void Main(string[] args)
{
SafeList<int> l = new SafeList<int>();
Stopwatch watch = new Stopwatch();
watch.Reset();
watch.Start();
for (int i=0; i < totalCount; i++)
{
l.Add(i);
}
watch.Stop();
Console.WriteLine(string.Format("list<T>添加{0}项耗时:{1}ms", totalCount,watch.ElapsedMilliseconds)); watch.Reset();
watch.Start();
for (int i = 0; i < totalCount; i=i+10000)
{
l.Find(i);
}
watch.Stop();
Console.WriteLine(string.Format("list<T>搜索{0}项耗时:{1}ms", totalCount/10000, watch.ElapsedMilliseconds)); //foreach (int item in l.ItemList)
//{
// if (item == 2)
// l.Remove(item);
//} //foreach (int item in l.ItemList)
//{
// Console.WriteLine(item);
//} Console.Read();
}
} class SafeItem<T>
{
private T _value;
public T Value
{
get { return _value; }
set { _value = value; }
}
private bool _isRemoved;
public bool IsRemoved
{
get { return _isRemoved; }
set { _isRemoved = value; }
}
} class SafeList<T>
{
List<SafeItem<T>> itemList = new List<SafeItem<T>>(); public void Add(T value)
{
SafeItem<T> o = new SafeItem<T>();
o.Value = value;
itemList.Add(o);
} public SafeItem<T> Find(T value)
{
return itemList.Find(delegate(SafeItem<T> o) { return o.Value.Equals(value); });
} public bool Remove(T value)
{
SafeItem<T> item = itemList.Find(delegate(SafeItem<T> o) { return o.Value.Equals(value); });
if (item != null)
{
item.IsRemoved = true;
return true;
}
else
{
return false;
}
} public IEnumerable<T> ItemList
{
get
{
itemList.RemoveAll(delegate(SafeItem<T> o) { return o.IsRemoved; });
foreach (SafeItem<T> item in itemList)
{
if (item.IsRemoved)
continue;
yield return item.Value;
}
}
}
}
}
执行结果
list<T>插入100万,126ms---没问题
list<T>中检索100条,1643ms--完全不靠谱。list<T>的find和hash表是没法比啊。我没有做先remove再 foreach测试,因为remove中也用了find,速度一定是惨不忍睹。那个IEnumerable接口的实现也用了find速度也不用提了,所以这个解决方案我用不了,不过还是再次感谢了。
如果有资料可以邮件..索取[email protected]
[code]//以下方法可以写在HttpModule的OnPreRequestHandlerExecute事件中
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute);
}
//你可以设sessionTimeout=30
HttpContext.Current.Cache.Insert(key, value, null, DateTime.MaxValue, sessionTimeout, CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(CacheItemRemovedCallbackMethod));private void CacheItemRemovedCallbackMethod(string key, object value, CacheItemRemovedReason reason)
{
//当cache过期
if (reason == CacheItemRemovedReason.Expired)
{
//处理你的dictionary
}
}
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute);
}
private void OnPreRequestHandlerExecute(object sender, EventArgs e)
{ // Ensure have a HttpContext
if (HttpContext.Current == null)
{
return;
}
//key,value根据需要定义
HttpContext.Current.Cache.Insert(key, value, null, DateTime.MaxValue, new TimeSpan(30), CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(CacheItemRemovedCallbackMethod));
}
private void CacheItemRemovedCallbackMethod(string key, object value, CacheItemRemovedReason reason)
{
if (reason == CacheItemRemovedReason.Expired)
{
//处理dictionary逻辑
}
}
看了下,在这个项目里用不到,基本上和Dictionary差不多,非托管的代码,IO还多了一道手续。也不知道是不是线程安全的,链表实现同样要自己进行,如果不用链表,还是解决不了过期检查的问题。可能在其它项目中有用,感觉还是不错的。cache 用时间依赖来解决过期确实是个好想法这样就不用做链表了。有空我测一下效率(我怀疑cache的过期 是用事件实现的,这样就依赖于系统的线程池有可能效率上还不如我的Dictionary链表),要是比Dictionary加锁做链表快,可以改用它。
队列的随机查询...
AggregateCacheDependency agg = new AggregateCacheDependency();
object j = new object();
j = (object)"1";
theCache.Insert("1", j, null, DateTime.Now.AddSeconds(30), new TimeSpan(30000000));
并且使用 List<string> 来记录所有设置false的键值
再遍历List 删除所有包含的 Dictionary 键虽然效率低了点 可能要进行2次循环 但这个方法也是最浅显易懂的
但是我的程序是要考虑线程安全的。你的提议做法中在dictionary中foreach设置value中的标志值的时候,我必须对全字典加锁,并生成一个新的值为flase的list。保持对dictionary中的锁并再次遍历list,得到值,remov不要的节点。在这个加锁期内做了一次dictionary遍历,一次list遍历。对n个节点进行remove操作。感觉可能会让dictionary失去响应其它线程的时间过长。
只要remove了 然后在把 i-- 这样也行
但是foreach 好像控制不了 这个迭代的次序,,无法让他回滚到上一个元素 所以他考虑到这个而保护了字典不让删除吧,,,,
这个我也不是很明白,,,但按逻辑来看 应该是这样,所以 如果你真的要控制他的话 建议你使用XML 用xPath
去定位也比用 Dictionary 这样让你头疼好,,,不过既然你是网络程序 你也要考虑 XML 不能同时操作的问题。可以使用缓存数据 然后用某一个线程统一插入当然这只是建议,,最终决定还要看你自己
他就是一个定位的 你直接定位到他的元素,,把他的元素名作为键值是一摸一样的但是每秒10万次读写,,如果数据量小可以,,,百万级 难……
其实还有个办法,,但是这样的话 可能冗余更大,,好像不如List方法
每次foreac 找到那个元素以后 使用break 跳出来 然后删除这个key 并且把他扔到递归里 让递归再次去跑,,直到跑完停止递归
这个我要没记错的话,好象递归层数是有限制的啊,一般栈2k的好象只支持2048层递归,以你的这办法一次删除不到3000个程序就over了。
也许会给你带来点灵感!http://www.cnblogs.com/moozi/archive/2010/05/22/1741610.html
看了下,这个类在处理过期时间是,首先来了个遍历判断(虽然是用linq但实质上还是遍历)拷出来一个符合过期标准list,然后锁字典进行移除。我上次说过这个遍历是11ms左右(加上判断不知道多长时间),大约还可以接受,不过每秒都做应该是不行的。我看这个类是做了个30分钟清理,平时过期的session就存着。这个做法和我的链表的区别是,链表是按的时间关系是放好的(这消耗了节点出入字典的时间),但判断过期时,只要锁字典从尾部一个节点一个节点删除就行了。另外,这个类在get的时候好象没有考虑到session时间的更新。个人感觉有一定参考性,不过效率上和现有的方案比,没有大的提高。
比如这样
public static Dictionary<string, Myclass> A = new Dictionary<string, Myclass>();
public static Dictionary<string, Myclass> B = new Dictionary<string, Myclass>();
public static DateTime LastSwtich = DateTime.Now;
public static TimeSpan TimeOut = new TimeSpan(0, 0, 30);
public static int MaxCapacity = 1000000; public static Myclass Get(string userid)
{
if (A.ContainsKey(userid))
{
Myclass m = A[userid];
if (DateTime.Now - m.thelogintime > TimeOut)
{
A.Remove(userid);
return null;
}
else
{
m.thelogintime = DateTime.Now;
return m;
}
}
else if (B.ContainsKey(userid))
{
Myclass m = B[userid];
if (DateTime.Now - m.thelogintime > TimeOut)
{
return null;
}
else
{
m.thelogintime = DateTime.Now;
A[userid] = m;
return m;
}
}
else
return null;
} public static void Set(string userid, Myclass user)
{
if (A.Count >= MaxCapacity && DateTime.Now - LastSwtich > TimeOut)
{
Dictionary<string, Myclass> temp = A;
A = B;
B = temp;
A.Clear();
}
user.thelogintime = DateTime.Now;
A[userid] = user;
}
当然多线程情况下还要考虑同步,只是个意思,lz参考下
LastSwtich = DateTime.Now;
补上
A = B;
B = temp;
A.Clear();
不好意思,我没看明白您的程序想做的是干什么,特别是这一段。你定义了一个 temp 指向了 A 指向的字典对象(DA)。又重新定义A指向B指向的对象(DB),又让B指向temp指向的字典对象(DA),也就是交换了一下AB两个字典,然后把A清除了。可这有什么意义呢?每次A都是空的,只加一条,把含这一条的字典交给B。B里永远只有一条啊?搞不明白您的意思。
if (A.Count >= MaxCapacity && DateTime.Now - LastSwtich > TimeOut)这个条件没看到吗?不是很难理解吧
我看LZ的应用,像是做缓存,过期后清理,并且,命中之后,这个过期时间是可以被修改的。
LZ使用链表大概是希望每次清理的时候不用全部遍历,只要遍历一部分做清理就可以。
如果是这样的话,我觉得可以从两个思路来入手。首先需要做的是,把时间按照一定的单位粒度离散化。1、使用SortedDictionary,或自己实现的优先队列来做。
2、直接开一个数组,比如粒度是5秒,开10000长的数组就可以管10多个小时,一般够用,每5秒处理1次,只处理数组中的一个元素即可。
看明白了啊,当a的总数超过100万了,而且距离上次清理超出了你设定的时限了,此时,你把A和B交换了一下,清了。可我还是没搞明白你这么做是在做什么吧。我来说一下我的理解啊,你的get是先在A里找,A里没有就在B里找,B里就返回的同时在A里也建一个。你的set是在每次添加或更新前都判断一下,都判断一下当a的总数超过100万了,而且距离上次清理超出了你设定的时限了,此时,你把A和B交换了一下,清了。可这有什么用啊,我不明白,请您详细解释一下。以过期时间三十分钟为例,我第一分钟放的和第二十九分钟放的,你这个程序怎么判断呢。请仔细看一下我的贴子,以及后面的补充。还有此问题在于解决多线程的情况,单线程的我就不考虑了,谢谢。
LinkedList无法直接命中。SortedDictionary 无法用value排序,而key值是不能变的。按时间间隔离散化的方法我想过,用循环队列(用字典加头尾指针的方法实现的),value中再加字典的方法。缺点是当时间粒度小和过期时间大的时候,这个两级dictionary结构不好,并且效率不高。一个读操作时要把一个节点在两个字典间操作。
是这样的,把A称为主缓存,B称为候补缓存吧。
查找时在A中查找,如果存在并且不过期,那么更新时间戳并返回结果就行。(我的理解你的要求应该是这样)
如果存在并已过期,那么在A中删除该条记录并返回null。
如果不存在,那么在B中再查找一次,如果存在并且不过期,那么更新时间戳,将该条记录添加到A中并返回结果。
如果存在并已过期,那么返回null。(B中也可以删除该条记录)
如果A和B中都不存在,那么当然返回null。在添加记录的时候,首先检查主缓存A是否达到容量上限。
如果未达到上限那就不用说了。
如果A已达到上限,那么再检查一下候补缓存B是否已过期(B过期的话,意味着B里面的数据全都过期了,也就没有意义了)。如果2者都满足,那么把当前主缓存切换为候补缓存,再重新生成新的主缓存。
可能这样写lz就不会有歧义了,其实是等价的
B = A;
A = new Dictionary<string,Myclass>();多线程的话简单一点把整个方法加锁就行了。
lz的思路是定期清理过期数据,其实在存、取数据时检查是否过期就可以了,不必非得实时清理过期数据。.Net自带的Cache应该也是这个做法。
public static Dictionary<string, Myclass> B = new Dictionary<string, Myclass>();
public static DateTime LastSwtich = DateTime.Now;
public static TimeSpan TimeOut = new TimeSpan(0, 30, 0);
public static int MaxCapacity = 1000000; public static Myclass Get(string userid)
{
if (A.ContainsKey(userid))
{
Myclass m = A[userid];
if (DateTime.Now - m.TimeStamp > TimeOut)
{
A.Remove(userid);
return null;
}
else
{
m.TimeStamp = DateTime.Now;
return m;
}
}
else if (B.ContainsKey(userid))
{
Myclass m = B[userid];
if (DateTime.Now - m.TimeStamp > TimeOut)
{
return null;
}
else
{
m.TimeStamp = DateTime.Now;
A[userid] = m;
return m;
}
}
else
return null;
} public static void Set(string userid, Myclass user)
{
if (A.Count > MaxCapacity && DateTime.Now - LastSwtich > TimeOut)
{
B = A;
A = new Dictionary<string,Myclass>();
LastSwtich = DateTime.Now;
}
user.thelogintime = DateTime.Now;
A[userid] = user;
}
} public class Myclass
{
public DateTime TimeStamp;
public DateTime thelogintime;
public bool loginflag = true;
}
应该是
user.TimeStamp = DateTime.Now;
list 的 find 效率低的惊人
(缺点就是把线程瓶颈转移到新增的队列操作上了)如:
public class myclass
{
public string key;
public DateTime thelogintime;
public bool loginflag = true;
}public class myclassLog
{
public myclassLog(DateTime logtime, myclass tag)
{
Logtime = logtime;
Tag = tag;
}
public DateTime Logtime { get; set; }
public myclass Tag { get; set; }
}public class ClassTest
{
Dictionary<string, myclass> dic;
Queue<myclassLog> queue;
object objDicLock = new object();
object objQueueLock = new object(); public ClassTest()
{
dic = new Dictionary<string, myclass>();
queue = new Queue<myclassLog>();
} // --------------- 例: ---------------
public void Add(myclass item)
{
lock (objDicLock)
{
dic[item.key] = item;
setlog(item);
}
} public void Update(string key)
{
myclass item = dic[key];
if (item.loginflag)
{
lock (objQueueLock)
{
if (item.loginflag)
{
item.thelogintime = DateTime.Now;
setlog(item);
}
}
}
}
// ------------------------------------
public void setlog(myclass tag)
{
lock (objQueueLock)
{
queue.Enqueue(new myclassLog(tag.thelogintime, tag));
}
} public void clear()
{
while (queue.Peek().Logtime <= DateTime.Now.AddSeconds(-30))
{
myclassLog log;
lock (objQueueLock)
{
log = queue.Dequeue();
}
if (log.Logtime == log.Tag.thelogintime)
{
lock (log.Tag)
{
if (log.Logtime == log.Tag.thelogintime)
{
log.Tag.loginflag = false;
lock (objDicLock)
{
dic.Remove(log.Tag.key);
}
}
}
}
}
}
}
此外,如果不离散化的处理,那么必然要在session上放时间,则每个读操作都将变成写操作。
{
//当前登录的opl系统唯一用户标识
public string OPLUserID;
//定义一些其它的内容
public string var1;
public string var2;
public string var3;
} class OPLSessionLinkTableNode
{
//指定前一个结点的sessionid//
private string _LeftNodeSessionID; public string LeftNodeSessionID
{
get { return _LeftNodeSessionID; }
set { _LeftNodeSessionID = value; }
}
//指定后一个结点的sessionid
private string _NextNodeSessionID; public string NextNodeSessionID
{
get { return _NextNodeSessionID; }
set { _NextNodeSessionID = value; }
} //当前session的生成时间
private DateTime _SessionTime = new DateTime(); public DateTime SessionTime
{
get { return _SessionTime; }
set { _SessionTime = value; }
} public OPLSession theSessioninfo = new OPLSession();
} class OPLSessionLinkTable
{
//session过期时间
private TimeSpan _theSessonTimeOut = new TimeSpan(0); //session过期时间指定到秒
public int TheSessonTimeOut
{
get { return (int)Math.Floor( _theSessonTimeOut.TotalSeconds ) ; }
set { _theSessonTimeOut= new TimeSpan(value * 10000000); }
}
//链表的锁
private readonly object syncRoot = new object(); //链表结束的指针(最新的一个)
private string _EndPoint = "";
public string EndPoint
{
get { return _EndPoint; }
} //链表开始的指针(最旧的一个)
private string _BeginPoint = "";
public string BeginPoint
{
get { return _BeginPoint; }
} //链表的字典存储结构 第一个为sessionid
private Dictionary<string, OPLSessionLinkTableNode> theOPLSessionDictionary = new Dictionary<string, OPLSessionLinkTableNode>(); internal Dictionary<string, OPLSessionLinkTableNode> TheOPLSessionDictionary
{
get { return theOPLSessionDictionary; }
}
/// <summary>
/// 在当前单点登录字典链表中添加一个session
/// </summary>
/// <param name="inp_SessionInfo">要添加的session信息</param>
/// <returns>sessionid</returns>
public string Add(OPLSession inp_SessionInfo, int i)
{
OPLSessionLinkTableNode theAddNode = new OPLSessionLinkTableNode();
string temp_SessionID = System.Guid.NewGuid().ToString();
//链表加锁
lock (syncRoot)
{
if (_EndPoint != "")
{
OPLSessionLinkTableNode theEndNode;
if (theOPLSessionDictionary.TryGetValue(_EndPoint, out theEndNode))
{
theAddNode.LeftNodeSessionID = _EndPoint;
theAddNode.NextNodeSessionID = "";
theAddNode.theSessioninfo = inp_SessionInfo;
theAddNode.SessionTime = DateTime.Now;
try
{
//theOPLSessionDictionary.Add(temp_SessionID, theAddNode);
theOPLSessionDictionary.Add(i.ToString(), theAddNode);
}
catch
{
//不成功不做处理,但丢失一个添加点。
return "";
}
//添加成功了
//_EndPoint = theEndNode.NextNodeSessionID = temp_SessionID;
//return temp_SessionID;
_EndPoint = theEndNode.NextNodeSessionID = i.ToString();
return i.ToString();
}
else
{
//致命异常,无法找到最后点!
return "";
}
}
else
{
if (_BeginPoint != "")
{
//致命异常,无法找到最后点,但有开始点,链表断了
return "";
}
else
{
//既无开始点,也无结束点。插入的是第一个点
theAddNode.LeftNodeSessionID = "";
theAddNode.NextNodeSessionID = "";
theAddNode.theSessioninfo = inp_SessionInfo;
theAddNode.SessionTime = DateTime.Now;
try
{
//theOPLSessionDictionary.Add(temp_SessionID, theAddNode);
theOPLSessionDictionary.Add(i.ToString(), theAddNode);
}
catch
{
//不成功不做处理,但丢失一个添加点。
return "";
}
//添加成功了
//_EndPoint = _BeginPoint = temp_SessionID;
//return temp_SessionID;
_EndPoint = _BeginPoint = i.ToString();
return i.ToString();
}
}
}
}
}
/// 根据指定的SessionID查找,返回一个OPLSession
/// </summary>
/// <param name="inp_SessionID"></param>
/// <returns></returns>
public OPLSession CheckSession(string inp_SessionID)
{
OPLSessionLinkTableNode theMoveNode;
OPLSessionLinkTableNode theLeftNode;
OPLSessionLinkTableNode theNextNode;
OPLSessionLinkTableNode theEndNode;
//链表加锁
lock (syncRoot)
{
if (theOPLSessionDictionary.TryGetValue(inp_SessionID, out theMoveNode))
{
DateTime theNow = DateTime.Now; if (theNow - theMoveNode.SessionTime < _theSessonTimeOut)
{
theMoveNode.SessionTime = DateTime.Now; if (theMoveNode.LeftNodeSessionID != "")
{
//有前一个点
if (theOPLSessionDictionary.TryGetValue(theMoveNode.LeftNodeSessionID, out theLeftNode))
{
//有前一个点找到了
theLeftNode.NextNodeSessionID = theMoveNode.NextNodeSessionID;
}
else
{
//有前一个点没找到???
//链断了,致使错误
}
}
else
{
_BeginPoint = theMoveNode.NextNodeSessionID;
} if (theMoveNode.NextNodeSessionID != "")
{
if (theOPLSessionDictionary.TryGetValue(theMoveNode.LeftNodeSessionID, out theNextNode))
{
//有后一个点找到了
theNextNode.LeftNodeSessionID = theMoveNode.LeftNodeSessionID;
}
else
{
//有后一个点没找到???
//链断了,致使错误
}
}
else
{
_EndPoint = theMoveNode.LeftNodeSessionID;
} theMoveNode.NextNodeSessionID = "";
theMoveNode.LeftNodeSessionID = _EndPoint;
_EndPoint = inp_SessionID;
return theMoveNode.theSessioninfo;
}
else
{
//超时了
if (theMoveNode.LeftNodeSessionID != "")
{
//有前一个点
if (theOPLSessionDictionary.TryGetValue(theMoveNode.LeftNodeSessionID, out theLeftNode))
{
//有前一个点找到了
theLeftNode.NextNodeSessionID = theMoveNode.NextNodeSessionID;
}
else
{
//有前一个点没找到???
//链断了,致使错误
}
}
else
{
_BeginPoint = theMoveNode.NextNodeSessionID;
} if (theMoveNode.NextNodeSessionID != "")
{
if (theOPLSessionDictionary.TryGetValue(theMoveNode.LeftNodeSessionID, out theNextNode))
{
//有后一个点找到了
theNextNode.LeftNodeSessionID = theMoveNode.LeftNodeSessionID;
}
else
{
//有后一个点没找到???
//链断了,致使错误
}
}
else
{
_EndPoint = theMoveNode.LeftNodeSessionID;
} try
{
theOPLSessionDictionary.Remove(inp_SessionID);
}
catch
{
//不成功不做处理,但丢失一个session。
return null;
} return null;
} }
else
{
return null;
}
}
}
/// <summary>
/// 在当前单点登录字典链表中删除一个session
/// </summary>
/// <param name="inp_SessionInfo">要删除的SessionID</param>
/// <returns></returns>
public bool Del(string inp_SessionID)
{
OPLSessionLinkTableNode theDelNode;
OPLSessionLinkTableNode theLeftNode;
OPLSessionLinkTableNode theNextNode; string temp_SessionID = System.Guid.NewGuid().ToString();
//链表加锁
lock (syncRoot)
{
if (theOPLSessionDictionary.TryGetValue(inp_SessionID, out theDelNode))
{
if (theDelNode.LeftNodeSessionID == "")
{
//不存在左点,则本点的右点为开始点
_BeginPoint = theDelNode.NextNodeSessionID;
}
else
{
//存在左点
if (theOPLSessionDictionary.TryGetValue(theDelNode.LeftNodeSessionID, out theLeftNode))
{
//找到了左点
theLeftNode.NextNodeSessionID = theDelNode.NextNodeSessionID;
}
else
{
//有左点但没找到,没找到左点
//产生致使错误 链表断了
}
}
if (theDelNode.NextNodeSessionID == "")
{
//不存在右点,则本点的左点为结束点
_EndPoint = theDelNode.LeftNodeSessionID;
}
else
{
//存在右点
if (theOPLSessionDictionary.TryGetValue(theDelNode.NextNodeSessionID, out theNextNode))
{
//找到了右点
theNextNode.LeftNodeSessionID = theDelNode.LeftNodeSessionID;
}
else
{
//有右点但没找到,没找到右点
//产生致使错误 链表断了
}
}
try
{
theOPLSessionDictionary.Remove (temp_SessionID);
}
catch
{
//不成功不做处理,但产生一个脏点,不在链表内。
}
//添加成功了
return true;
}
else
{
//未找到要删除的点
return false;
}
}
} public int Count
{
get { return theOPLSessionDictionary.Count; }
}
我还是那个建议,在get的时候检查是否过期,不要另开线程也不要遍历清理过期数据
我明白你的意思,不过我在这儿每秒清理的初衷是为了实现一个session onend的事件(不是事件,就是让session onend的时候能做点事,精确到一秒就行),所以才考虑定时每秒清理的。这点可能是我没说明白。
List一直由单一线程管理,自然用不着锁,dictionary如果因为锁的问题,会出现什么情况,LZ现在的方案也同样存在这个问题。
读的时候为什么不改list<>[]中的值呢。list<>[]不是以登录时间片划分的吗。设过期时间1分钟,划分60片。则在第一秒登录的session放在list<>[1]中。第十秒放在list<>[10]中,但当第一秒产生的session在第十五秒发生了一次读的时候,这个session必须要移到list<>[15]中啊,要不然这个list<>[]还有什么意义。
lz的thelogintime实际上就是最后访问时间了。
不知道lz有没有明白我的意思,简单来说就是2个Dictionary A和B,假设过期时间是半小时的话。
每过半个小时把B中的数据淘汰,A中的数据移至B,同时给A赋一个新的Dictionary。
每次访问先检查A再检查B,如果在B中并且没过期的话要把该条记录移至A中。这样能明白了?
1:不直接for each 后删除, 而是for each后经过比较 找到过期数据的key,用list保存,保存完毕后再从list中读取数据删除。
2:不需要及时真的删除的话可以在取数据是判断是否过期,若过期就删除。但是还有部分数据很长时间不会被用到,这时候一分钟处理一次,可以建一个30个list,表示未来三十分钟需要删除的key,每次添加数据的时候判断过期时间,若过期时间是15分钟,则将这个key放入第15个list,时间到了就将该list保存的key的数据都删除。30分钟后过期的key放入一个list,在每分钟检查过期的时候同时检查30分钟过期的那个list
改一下
myclass{
int thelogintime; //登录的系统毫秒 System.Environment.TickCount
bool loginflag
{
get{ return (thelogintime -System.Environment.TickCount) >=30*1000}
}
}
没有必要更新它,比如设置5分钟循环一次集合,是否要超时的,超时的移除掉.
定时从队列尾部去除过期的对象。
确切的说,我是要实现跟cache的功能相近,但对效率要求的优先级更高。