使用ReaderWriterLock优化文件缓存

    公司使用的文件缓存经常出现大量的并发冲突,主要原因有两个:一个是读取文件时刚好文件被删除了,这样会抛出找不到文件的异常; 另一个是资源权限争抢的问题,可能会导致没有权限操作的情况。
    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();
//释放读锁
            }

        }

2、添加缓存文件时使用AcquireWriterLock方法获取写锁。
        public void Add(string key, object value)
        
{
            
if (key == nullreturn;
            
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 == nullreturn;
            
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);
            }

        }

    }

}

测试使用了Interlocked类来同步记录总共的读写次数,使用lock来同步记录总共耗时。
注意使用Thread.Sleep(10);可以保证测试程序的并发量,防止在一个非常小的一个时间片中就耗掉大量的测试操作。
原文地址:https://www.cnblogs.com/chenjunbiao/p/1760222.html