使用ReaderWriterLock优化文件缓存
公司使用的文件缓存经常出现大量的并发冲突,主要原因有两个:一个是读取文件时刚好文件被删除了,这样会抛出找不到文件的异常; 另一个是资源权限争抢的问题,可能会导致没有权限操作的情况。
ReaderWriterLock类实现了多用户读/单用户写的同步访问机制,可以使用此类对文件的读写进行加锁操作,因为文件缓存一般是大量读少量写的情况,所以非常适合使用ReaderWriterLock。
1、读缓存文件时使用AcquireReaderLock方法,并设置1000ms超时操作。
2、添加缓存文件时使用AcquireWriterLock方法获取写锁。
3、在删除缓存文件时也通过AcquireWriterLock获取写锁。
另外还写了一个测试程序对优化了的文件缓存进行并发访问测试。
测试使用了Interlocked类来同步记录总共的读写次数,使用lock来同步记录总共耗时。
注意使用Thread.Sleep(10);可以保证测试程序的并发量,防止在一个非常小的一个时间片中就耗掉大量的测试操作。
ReaderWriterLock类实现了多用户读/单用户写的同步访问机制,可以使用此类对文件的读写进行加锁操作,因为文件缓存一般是大量读少量写的情况,所以非常适合使用ReaderWriterLock。
1、读缓存文件时使用AcquireReaderLock方法,并设置1000ms超时操作。
private string readfile(string filename)
{
FileStream fs = null;
try
{
rwLock.AcquireReaderLock(1000);//获取读锁
fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
byte[] buf = new byte[fs.Length];
fs.Read(buf, 0, (int)fs.Length);
string xml = Encoding.UTF8.GetString(buf);
return xml;
}
catch (FileNotFoundException)//2008-01-24 add
{
return null;
}
finally
{
if (fs != null)
{
fs.Close();
fs.Dispose();
fs = null;
}
rwLock.ReleaseReaderLock();//释放读锁
}
}
{
FileStream fs = null;
try
{
rwLock.AcquireReaderLock(1000);//获取读锁
fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
byte[] buf = new byte[fs.Length];
fs.Read(buf, 0, (int)fs.Length);
string xml = Encoding.UTF8.GetString(buf);
return xml;
}
catch (FileNotFoundException)//2008-01-24 add
{
return null;
}
finally
{
if (fs != null)
{
fs.Close();
fs.Dispose();
fs = null;
}
rwLock.ReleaseReaderLock();//释放读锁
}
}
2、添加缓存文件时使用AcquireWriterLock方法获取写锁。
public void Add(string key, object value)
{
if (key == null) return;
if (value == null)
{
Remove(key);
return;
}
string fn = makefilename(makekey(key));
string dir = Path.GetDirectoryName(fn);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
string xml = makevalue(value);
//File.WriteAllText(fn, xml, Encoding.UTF8);
FileStream fs = null;
try
{
rwLock.AcquireWriterLock(1000);//获取写锁
fs = new FileStream(fn, FileMode.Create, FileAccess.Write, FileShare.Read);
byte[] buf = Encoding.UTF8.GetBytes(xml);
fs.Write(buf, 0, buf.Length);
}
finally
{
if (fs != null)
{
fs.Close();
fs.Dispose();
fs = null;
}
rwLock.ReleaseWriterLock();//释放写锁
}
}
{
if (key == null) return;
if (value == null)
{
Remove(key);
return;
}
string fn = makefilename(makekey(key));
string dir = Path.GetDirectoryName(fn);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
string xml = makevalue(value);
//File.WriteAllText(fn, xml, Encoding.UTF8);
FileStream fs = null;
try
{
rwLock.AcquireWriterLock(1000);//获取写锁
fs = new FileStream(fn, FileMode.Create, FileAccess.Write, FileShare.Read);
byte[] buf = Encoding.UTF8.GetBytes(xml);
fs.Write(buf, 0, buf.Length);
}
finally
{
if (fs != null)
{
fs.Close();
fs.Dispose();
fs = null;
}
rwLock.ReleaseWriterLock();//释放写锁
}
}
3、在删除缓存文件时也通过AcquireWriterLock获取写锁。
public void Remove(string key)
{
if (key == null) return;
string fileName = makefilename(makekey(key));
try
{
rwLock.AcquireWriterLock(1000);//获取写锁
File.Delete(fileName);
}
finally
{
rwLock.ReleaseWriterLock();//释放写锁
}
}
{
if (key == null) return;
string fileName = makefilename(makekey(key));
try
{
rwLock.AcquireWriterLock(1000);//获取写锁
File.Delete(fileName);
}
finally
{
rwLock.ReleaseWriterLock();//释放写锁
}
}
另外还写了一个测试程序对优化了的文件缓存进行并发访问测试。
//#define usetry
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
namespace CJB.Caching.FileSystem.Test
{
class Program
{
static long ElapsedTime = 0;
static readonly object ElapsedTimeLock = new object();
static long Counter = 0;
static void Main(string[] args)
{
Thread t1 = new Thread(ReadTest);
Thread t2 = new Thread(ReadTest);
Thread t3 = new Thread(ReadTest);
Thread t4 = new Thread(WriteTest);
Thread t5 = new Thread(WriteTest);
t1.Start(); //t1.Join();
t2.Start(); //t2.Join();
t3.Start(); //t3.Join();
t4.Start(); //t4.Join();
t5.Start(); //t5.Join();
Console.ReadLine();
Console.WriteLine(String.Format("程序完成调用{0}次数", Counter));
Console.WriteLine(String.Format("程序运行时间{0}毫秒", ElapsedTime));
while (Console.ReadLine() != "q")
{
Console.WriteLine("请输入q退出程序");
}
}
static void ReadTest()
{
TCache<string> tCache = new TCache<string>(TCacheType.FSCacheTest);
string cacheKey = tCache.GetCacheKey("billok");
Stopwatch sw = null;
for (int i = 0; i < 1000; i++)
{
sw = Stopwatch.StartNew();
#if(usetry)
try
{
#endif
string result = tCache.GetValue(cacheKey, delegate() { return "ReadTest" + i.ToString(); });
Console.WriteLine(result);
#if(usetry)
}
catch { }
#endif
long ms = sw.ElapsedMilliseconds;
lock (ElapsedTimeLock)
{
ElapsedTime += ms;
}
Interlocked.Increment(ref Counter);
Thread.Sleep(10);
}
}
static void WriteTest()
{
TCache<string> tCache = new TCache<string>(TCacheType.FSCacheTest);
string cacheKey = tCache.GetCacheKey("billok");
Stopwatch sw = null;
for (int i = 0; i < 1000; i++)
{
sw = Stopwatch.StartNew();
#if(usetry)
try
{
#endif
tCache.Remove(cacheKey);
Console.WriteLine("删除" + cacheKey);
#if(usetry)
}
catch { }
#endif
long ms = sw.ElapsedMilliseconds;
lock (ElapsedTimeLock)
{
ElapsedTime += ms;
}
Interlocked.Increment(ref Counter);
Thread.Sleep(10);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
namespace CJB.Caching.FileSystem.Test
{
class Program
{
static long ElapsedTime = 0;
static readonly object ElapsedTimeLock = new object();
static long Counter = 0;
static void Main(string[] args)
{
Thread t1 = new Thread(ReadTest);
Thread t2 = new Thread(ReadTest);
Thread t3 = new Thread(ReadTest);
Thread t4 = new Thread(WriteTest);
Thread t5 = new Thread(WriteTest);
t1.Start(); //t1.Join();
t2.Start(); //t2.Join();
t3.Start(); //t3.Join();
t4.Start(); //t4.Join();
t5.Start(); //t5.Join();
Console.ReadLine();
Console.WriteLine(String.Format("程序完成调用{0}次数", Counter));
Console.WriteLine(String.Format("程序运行时间{0}毫秒", ElapsedTime));
while (Console.ReadLine() != "q")
{
Console.WriteLine("请输入q退出程序");
}
}
static void ReadTest()
{
TCache<string> tCache = new TCache<string>(TCacheType.FSCacheTest);
string cacheKey = tCache.GetCacheKey("billok");
Stopwatch sw = null;
for (int i = 0; i < 1000; i++)
{
sw = Stopwatch.StartNew();
#if(usetry)
try
{
#endif
string result = tCache.GetValue(cacheKey, delegate() { return "ReadTest" + i.ToString(); });
Console.WriteLine(result);
#if(usetry)
}
catch { }
#endif
long ms = sw.ElapsedMilliseconds;
lock (ElapsedTimeLock)
{
ElapsedTime += ms;
}
Interlocked.Increment(ref Counter);
Thread.Sleep(10);
}
}
static void WriteTest()
{
TCache<string> tCache = new TCache<string>(TCacheType.FSCacheTest);
string cacheKey = tCache.GetCacheKey("billok");
Stopwatch sw = null;
for (int i = 0; i < 1000; i++)
{
sw = Stopwatch.StartNew();
#if(usetry)
try
{
#endif
tCache.Remove(cacheKey);
Console.WriteLine("删除" + cacheKey);
#if(usetry)
}
catch { }
#endif
long ms = sw.ElapsedMilliseconds;
lock (ElapsedTimeLock)
{
ElapsedTime += ms;
}
Interlocked.Increment(ref Counter);
Thread.Sleep(10);
}
}
}
}
测试使用了Interlocked类来同步记录总共的读写次数,使用lock来同步记录总共耗时。
注意使用Thread.Sleep(10);可以保证测试程序的并发量,防止在一个非常小的一个时间片中就耗掉大量的测试操作。