データベースに格納されているさまざまなテーブルからレコードを読み取る .Net 3.5 を使用してマルチスレッド アプリケーションを開発しています。読み取りは非常に頻繁に行われるため、遅延読み込みキャッシュの実装が必要です。すべてのテーブルは C# クラスにマップされ、キャッシュでキーとして使用できる文字列列を持ちます。さらに、キャッシュされたすべてのレコードを定期的に更新する必要があります。スレッドセーフな環境を確保するために、すべての読み取りでロックを使用してキャッシュを実装することもできましたが、考えられるすべてのキーのリストを取得するのは簡単であるという事実に依存する別のソリューションを考えました。
これが私が書いた最初のクラスで、二重チェック ロック パターンで遅延読み込みされるすべてのキーのリストを格納します。また、最後に要求された更新のタイムスタンプを静的変数に格納するメソッドもあります。
public class Globals
{
private static object _KeysLock = new object();
public static volatile List<string> Keys;
public static void LoadKeys()
{
if (Keys == null)
{
lock (_KeysLock)
{
if (Keys == null)
{
List<string> keys = new List<string>();
// Filling all possible keys from DB
// ...
Keys = keys;
}
}
}
}
private static long refreshTimeStamp = DateTime.Now.ToBinary();
public static DateTime RefreshTimeStamp
{
get { return DateTime.FromBinary(Interlocked.Read(ref refreshTimeStamp)); }
}
public static void NeedRefresh()
{
Interlocked.Exchange(ref refreshTimeStamp, DateTime.Now.ToBinary());
}
}
次にCacheItem<T>
、キーによってフィルター処理された指定されたテーブル T のキャッシュの単一アイテムの実装であるクラスを作成しました。Load
レコード リストの遅延読み込み用のメソッドとLoadingTimeStamp
、最後のレコード読み込みのタイムスタンプを格納するプロパティがあります。レコードの静的リストは、ローカルに入力された新しいリストで上書きされ、その後 も上書きされることに注意してくださいLoadingTimeStamp
。
public class CacheItem<T>
{
private List<T> _records;
public List<T> Records
{
get { return _records; }
}
private long loadingTimestampTick;
public DateTime LoadingTimestamp
{
get { return DateTime.FromBinary(Interlocked.Read(ref loadingTimestampTick)); }
set { Interlocked.Exchange(ref loadingTimestampTick, value.ToBinary()); }
}
public void Load(string key)
{
List<T> records = new List<T>();
// Filling records from DB filtered on key
// ...
_records = records;
LoadingTimestamp = DateTime.Now;
}
}
最後Cache<T>
に、テーブル T のキャッシュを静的 Dictionary として格納するクラスを次に示します。ご覧のとおり、このGet
メソッドは、まだ実行されていない場合は最初にキャッシュ内の可能なすべてのキーをロードし、次に更新のためにタイムスタンプをチェックします (どちらも二重チェック ロック パターンで実行されます)。呼び出しによって返されたインスタンス内のレコードのリストはGet
、ロック内でリフレッシュを実行している別のスレッドが存在する場合でも、スレッドによって安全に読み取ることができます。これは、リフレッシュ スレッドがリスト自体を変更するのではなく、新しいリストを作成するためです。
public class Cache<T>
{
private static object _CacheSynch = new object();
private static Dictionary<string, CacheItem<T>> _Cache = new Dictionary<string, CacheItem<T>>();
private static volatile bool _KeysLoaded = false;
public static CacheItem<T> Get(string key)
{
bool checkRefresh = true;
CacheItem<T> item = null;
if (!_KeysLoaded)
{
lock (_CacheSynch)
{
if (!_KeysLoaded)
{
Globals.LoadKeys(); // Checks the lazy loading of the common key list
foreach (var k in Globals.Keys)
{
item = new CacheItem<T>();
if (k == key)
{
// As long as the lock is acquired let's load records for the requested key
item.Load(key);
// then the refresh is no more needed by the current thread
checkRefresh = false;
}
_Cache.Add(k, item);
}
_KeysLoaded = true;
}
}
}
// here the key is certainly contained in the cache
item = _Cache[key];
if (checkRefresh)
{
// let's check the timestamps to know if refresh is needed
DateTime rts = Globals.RefreshTimeStamp;
if (item.LoadingTimestamp < rts)
{
lock (_CacheSynch)
{
if (item.LoadingTimestamp < rts)
{
// refresh is needed
item.Load(key);
}
}
}
}
return item;
}
}
が定期的にGlobals.NeedRefresh()
呼び出され、レコードが確実に更新されます。このソリューションは、キャッシュが可能なすべてのキーで事前に埋められているため、キャッシュの読み取りごとにロックを回避できます。これは、可能なすべてのキーの数 (約 20) に等しい数のインスタンスがメモリに存在することを意味します。 ) 要求されたタイプ T ごとに (すべての T タイプは約 100 です)、要求されたキーについてのみ、レコード リストは空ではありません。この解決策にスレッドセーフの問題や問題がある場合はお知らせください。どうもうありがとう。