上一章我们讨论到了解决方案,本文着重讨论一下该方案的实现.
一. 排它锁类的实现:
我们实现一个类TimeSpanWaitor,用它来控制排它锁的获取和释放,其实该类的实现的目的很简单,那就是实现一个方法,该方法接受一个时间片段和一个函数回调(该函数返回bool)的参数,该方法在指定时间片段内不断的轮值排它信号,并在每次获得排它信号时调用传入的函数,当该函数返回True时则返回,否则就一值轮值排它信号直到超时.
如下:1.轮值排它信号,该过程尝试获取排它信号,如果获取成功则调用回调函数,并根据回调函数返回相应状态.否则返回下一次继续的状态.
2.如果上一次轮值状态是返回继续则线程随机停顿后循环到1执行,否则退出
具体的代码如下:
View Code
1 public sealed class TimeSpanWaitor 2 { 3 4 public TimeSpanWaitor(int minwaitmillseconds, int maxwaitmillsecondes) 5 { 6 g_AsyncObject = new IntLock(); 7 g_DefaultWaitTime = new TimeSpan(0, 0, 1); 8 int min = minwaitmillseconds; 9 if (min < 0) 10 min = 10; 11 int max = maxwaitmillsecondes; 12 if (max < 0) 13 max = 100; 14 if (min > max) 15 { 16 int x = min; 17 min = max; 18 max = x; 19 } 20 if (min == max) 21 { 22 min = 10; 23 max = 100; 24 } 25 g_MaxWaitMillSeconds = max; 26 g_MinWaitMillSeconds = min; 27 g_WaitTimeDom = new Random(); 28 } 29 30 public TimeSpanWaitor() 31 : this(DefaultMinWaitTimeMillSeconds, DefaultMaxWaitTimeMillSeconds) 32 { 33 } 34 35 #region 公有常数 36 37 public const int DefaultMaxWaitTimeMillSeconds = 100; 38 39 public const int DefaultMinWaitTimeMillSeconds = 10; 40 41 #endregion 42 43 #region 私有常量 44 45 private IntLock g_AsyncObject; 46 47 private TimeSpan g_DefaultWaitTime; 48 49 private Random g_WaitTimeDom = null; 50 51 private int g_MaxWaitMillSeconds = 0; 52 53 private int g_MinWaitMillSeconds = 0; 54 55 #endregion 56 57 #region 私有方法 58 59 /// <summary> 60 /// 尝试锁定 61 /// </summary> 62 /// <param name="onenter">成功锁定时调用该回调:返回True指示退出获取锁定,否则继续下一次获取锁定</param> 63 /// <returns>尝试结果</returns> 64 private PerWaitEnum TryEnter(Func<bool> onenter) 65 { 66 bool success = g_AsyncObject.Lock(); 67 if (success) 68 { 69 PerWaitEnum r = PerWaitEnum.SuccessAndContinue; 70 Exception err = null; 71 try 72 { 73 if (onenter()) 74 r = PerWaitEnum.SuccessAndExists; 75 } 76 catch (Exception e) 77 { 78 err = e; 79 } 80 finally 81 { 82 g_AsyncObject.UnLock(); 83 } 84 if (err != null) 85 throw err; 86 return r; 87 } 88 return PerWaitEnum.Fail; 89 } 90 91 /// <summary> 92 /// 等待 93 /// </summary> 94 /// <param name="waittimeout">等待超时值</param> 95 /// <param name="dt">上次等待时间</param> 96 /// <returns>返回True指示未超时</returns> 97 private bool WaitTime(ref TimeSpan waittimeout, ref DateTime dt) 98 { 99 if (waittimeout == TimeSpan.MaxValue) 100 { 101 Thread.Sleep(g_WaitTimeDom.Next(g_MinWaitMillSeconds, g_MaxWaitMillSeconds)); 102 dt = DateTime.Now; 103 return true; 104 } 105 else if (waittimeout == TimeSpan.MinValue) 106 { 107 dt = DateTime.Now; 108 return false; 109 } 110 else if (waittimeout == TimeSpan.Zero) 111 { 112 dt = DateTime.Now; 113 return false; 114 } 115 else 116 { 117 Thread.Sleep(g_WaitTimeDom.Next(g_MinWaitMillSeconds, g_MaxWaitMillSeconds)); 118 waittimeout -= GetNowDateTimeSpan(ref dt); 119 return (waittimeout.Ticks > 0); 120 } 121 } 122 /// <summary> 123 /// 计算此时同tp的时间差,同时tp返回此时时间 124 /// </summary> 125 /// <param name="tp">上次等待时间,返回此时</param> 126 /// <returns>tp同此时的时间差</returns> 127 private TimeSpan GetNowDateTimeSpan(ref DateTime tp) 128 { 129 DateTime kk = tp; 130 tp = DateTime.Now; 131 return tp.Subtract(kk); 132 } 133 #endregion 134 135 #region 公有方法 136 137 /// <summary> 138 /// 等待指定的时间:timeout 139 /// </summary> 140 /// <param name="timeout">等待超时时间:该值=TimeSpan.MaxValue边示无期限的等待</param> 141 /// <param name="onenter">当每次获得等待锁时都调用,返回True表示退出等待,否则再次等待锁,直到超时</param> 142 /// <returns>True表示成功等待到锁并且onenter函数返回True,False:表示等待超时</returns> 143 public bool WaitForTime(TimeSpan timeout, Func<bool> onenter) 144 { 145 TimeSpan tmout = timeout; 146 DateTime n = DateTime.Now; 147 PerWaitEnum r = TryEnter(onenter); 148 while (r != PerWaitEnum.SuccessAndExists) 149 { 150 if (!WaitTime(ref tmout, ref n)) 151 break; 152 r = TryEnter(onenter); 153 } 154 return r == PerWaitEnum.SuccessAndExists; 155 } 156 #endregion 157 } 158 internal enum PerWaitEnum 159 { 160 SuccessAndExists, 161 SuccessAndContinue, 162 Fail 163 }
bool WaitForTime(TimeSpan timeout, Func<bool> onenter)方法为实现的方法,该方法timeout参数为超时值,参数onenter为一个返回bool的函数回调,它是在每次获取了排它信号时调用的,调用方可以在此回调内执行上一章解决方案中的步骤二:判断读写锁逻辑是否满足,如果满足则进行锁登记等等操作.该回调返回true则说明不必再去轮值排它锁了,也就是说调用方已经知道读写锁是否可以获取了.返回false说明本次轮值还不能确定是否可以获取,应该继续轮值.
其实该方法就如同如下函义:
lock(obj)
{
onenter();
} 不同之处在于,它是不断的轮值调用,直到调用方返回True或超时.调用方在onenter方法内时明白此时的所有数据都是同步的.
正如前面说的一样,WaitForTime方法分两个主要步骤,而其内部方法TryEnter则实现了步骤一,该方法根据是否取得排它锁及取得后回调函数的返回情况分别返回PerWaitEnum.SuccessAndContinue(继续),PerWaitEnum.SuccessAndExists(退出),PerWaitEnum.Fail(排它锁获取失败)三种状态, 而主函数WaitForTime则根据这三种状态判断是成功退出还是继续循环轮值还是失败返回,其中在每次循环前会调用WaitTime随机停顿一断时间.该类的排它信号采用了上一章介绍的IntLock类.这个类的名称有网友说有点不好,是的,这个名称确实不妥.
最后看看整个读写锁的主函数:
读写锁获取的主函数
1 public IDisposeState LockRead(TimeSpan timeout,Func<bool> isvalidstate) 2 { 3 IDisposeState rstate = null; 4 Func<bool> f = () => 5 { 6 if (g_Disposed) 7 { 8 rstate = DisposedState.Empty; 9 return true; 10 } 11 if (TryLockRead()) 12 { 13 bool isvalid = isvalidstate != null ? isvalidstate() : true; 14 if (!isvalid) 15 rstate = DisposedState.Empty; 16 else 17 rstate = 18 MutilThreadDisposeStatePools.GetMutilThreadDisposeState( 19 true, false, this); 20 return true; 21 } 22 else 23 { 24 return false; 25 } 26 }; 27 if (g_Lock.WaitForTime(timeout, f)) 28 return rstate; 29 else 30 return DisposedState.Empty; 31 } 32 33 public IDisposeState LockWrite(TimeSpan timeout, Func<bool> isvalidstate) 34 { 35 IDisposeState rstate = null; 36 Func<bool> f = () => 37 { 38 if (g_Disposed) 39 { 40 rstate = DisposedState.Empty; 41 return true; 42 } 43 if (TryLockWrite()) 44 { 45 bool isvalid = isvalidstate != null ? isvalidstate() : true; 46 if (isvalid) 47 rstate = 48 MutilThreadDisposeStatePools.GetMutilThreadDisposeState( 49 isvalid, true, this); 50 else 51 rstate = DisposedState.Empty; 52 return true; 53 } 54 else 55 { 56 return false; 57 } 58 }; 59 if (g_Lock.WaitForTime(timeout, f)) 60 return rstate; 61 else 62 return DisposedState.Empty; 63 }
这两个函数的结构一致,只是一个是用读锁逻辑(TryLockRead)判断,一个是用写(TryLockWrite)锁逻辑判断,它们的判断逻辑都在定义的函数体f内执行,执行函数体f时,此时类内部的数据是不会有其它线程改变的.并且在g_Lock.WaitForTime函数内会不断调用函数体f的,直到超时获函数体f返回true.而读写锁逻辑的实现我就不说了,这个比较好看明白.
对于多线程读写锁的一些讨论就先到此了,完整的类见博文:http://www.cnblogs.com/ren700622/archive/2011/05/23/2054455.html
|
请发表评论