MemoryCache 스레드 안전성, 잠금이 필요합니까?
우선, 아래 코드가 스레드로부터 안전하지 않다는 것을 알기 만하면됩니다 (수정 :있을 수 있음). 내가 고생하는 것은 실제로 테스트에서 실패 할 수있는 구현을 찾는 것입니다. 현재 일부 (대부분) 정적 데이터가 캐시되고 SQL 데이터베이스에서 채워지는 대규모 WCF 프로젝트를 리팩터링하고 있습니다. 적어도 하루에 한 번 만료되고 "새로 고침"해야하므로 MemoryCache를 사용하고 있습니다.
아래 코드가 스레드로부터 안전하지 않아야한다는 것을 알고 있지만 과부하 상태에서 실패하고 문제를 복잡하게 만들 수는 없습니다. Google 검색은 두 가지 방법으로 구현을 보여줍니다 (잠금이 필요한지 여부에 관계없이 토론과 결합 된 경우와없는 경우).
다중 스레드 환경에서 MemoryCache에 대한 지식이있는 사람이 적절한 위치에 잠 가야하는지 여부를 확실하게 알려 주어 제거 호출 (거의 호출되지 않지만 요구 사항 임)이 검색 / 재 채우기 중에 발생하지 않도록 할 수 있습니다.
public class MemoryCacheService : IMemoryCacheService
{
private const string PunctuationMapCacheKey = "punctuationMaps";
private static readonly ObjectCache Cache;
private readonly IAdoNet _adoNet;
static MemoryCacheService()
{
Cache = MemoryCache.Default;
}
public MemoryCacheService(IAdoNet adoNet)
{
_adoNet = adoNet;
}
public void ClearPunctuationMaps()
{
Cache.Remove(PunctuationMapCacheKey);
}
public IEnumerable GetPunctuationMaps()
{
if (Cache.Contains(PunctuationMapCacheKey))
{
return (IEnumerable) Cache.Get(PunctuationMapCacheKey);
}
var punctuationMaps = GetPunctuationMappings();
if (punctuationMaps == null)
{
throw new ApplicationException("Unable to retrieve punctuation mappings from the database.");
}
if (punctuationMaps.Cast<IPunctuationMapDto>().Any(p => p.UntaggedValue == null || p.TaggedValue == null))
{
throw new ApplicationException("Null values detected in Untagged or Tagged punctuation mappings.");
}
// Store data in the cache
var cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = DateTime.Now.AddDays(1.0)
};
Cache.AddOrGetExisting(PunctuationMapCacheKey, punctuationMaps, cacheItemPolicy);
return punctuationMaps;
}
//Go oldschool ADO.NET to break the dependency on the entity framework and need to inject the database handler to populate cache
private IEnumerable GetPunctuationMappings()
{
var table = _adoNet.ExecuteSelectCommand("SELECT [id], [TaggedValue],[UntaggedValue] FROM [dbo].[PunctuationMapper]", CommandType.Text);
if (table != null && table.Rows.Count != 0)
{
return AutoMapper.Mapper.DynamicMap<IDataReader, IEnumerable<PunctuationMapDto>>(table.CreateDataReader());
}
return null;
}
}
기본 MS 제공 MemoryCache
은 전적으로 스레드로부터 안전합니다. 파생 된 사용자 지정 구현은 MemoryCache
스레드로부터 안전하지 않을 수 있습니다. MemoryCache
상자에서 꺼내지 않은 일반 을 사용하는 경우 스레드로부터 안전합니다. 내 오픈 소스 분산 캐싱 솔루션의 소스 코드를 검색하여 사용 방법을 확인합니다 (MemCache.cs).
https://github.com/haneytron/dache/blob/master/Dache.CacheHost/Storage/MemCache.cs
MemoryCache는 다른 답변에서 지정한 것처럼 실제로 스레드로부터 안전하지만 일반적인 다중 스레딩 문제가 있습니다. 2 개의 스레드 가 동시에 캐시에서 캐시를 시도 Get
(또는 확인 Contains
)하면 둘 다 캐시를 놓치고 둘 다 생성됩니다. 결과와 둘 다 결과를 캐시에 추가합니다.
종종 이것은 바람직하지 않습니다. 두 번째 스레드는 첫 번째 스레드가 완료 될 때까지 기다렸다가 결과를 두 번 생성하는 대신 결과를 사용해야합니다.
이것이 제가 LazyCache를 작성한 이유 중 하나였습니다 . 이러한 종류의 문제를 해결하는 MemoryCache의 친숙한 래퍼입니다. Nuget 에서도 사용할 수 있습니다 .
다른 사람들이 말했듯이 MemoryCache는 실제로 스레드로부터 안전합니다. 그러나 그 안에 저장된 데이터의 스레드 안전성은 전적으로 사용하는 것에 달려 있습니다.
Reed Copsey의 동시성과 유형 에 관한 멋진 게시물 에서 인용 ConcurrentDictionary<TKey, TValue>
합니다. 물론 여기에 적용 할 수 있습니다.
두 스레드가이 [GetOrAdd]를 동시에 호출하면 TValue의 두 인스턴스를 쉽게 구성 할 수 있습니다.
TValue
건설 비용이 많이 든다 면 이것이 특히 나쁠 것이라고 상상할 수 있습니다 .
이 문제를 해결하기 위해 Lazy<T>
매우 쉽게 활용할 수 있으며, 이는 우연히 구축 비용이 매우 저렴합니다. 이렇게하면 다중 스레드 상황이 발생하는 경우 Lazy<T>
(저렴한) 여러 인스턴스 만 빌드 할 수 있습니다.
GetOrAdd()
( GetOrCreate()
의 경우 MemoryCache
)는 Lazy<T>
모든 스레드에 대해 동일하고 단수 를 반환하며 의 "추가"인스턴스 Lazy<T>
는 단순히 버려집니다.
(가)부터 Lazy<T>
때까지 아무것도하지 않습니다 .Value
라고, 객체의 하나의 인스턴스 만 지금까지 건설된다.
이제 몇 가지 코드입니다! 아래는 IMemoryCache
위를 구현 하는 확장 방법입니다 . 메소드 매개 변수 SlidingExpiration
에 따라 임의로 설정합니다 int seconds
. 그러나 이것은 귀하의 필요에 따라 완전히 사용자 정의 할 수 있습니다.
.netcore2.0 앱에만 해당됩니다.
public static T GetOrAdd<T>(this IMemoryCache cache, string key, int seconds, Func<T> factory)
{
return cache.GetOrCreate<T>(key, entry => new Lazy<T>(() =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(seconds);
return factory.Invoke();
}).Value);
}
전화하려면 :
IMemoryCache cache;
var result = cache.GetOrAdd("someKey", 60, () => new object());
이 모든 작업을 비동기 적으로 수행하려면 MSDN 에 대한 그의 기사 에 있는 Stephen Toub의 뛰어난 AsyncLazy<T>
구현을 사용하는 것이 좋습니다 . 내장 lazy 이니셜 라이저 와 promise를 결합합니다 .Lazy<T>
Task<T>
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<T> valueFactory) :
base(() => Task.Factory.StartNew(valueFactory))
{ }
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap())
{ }
}
이제 비동기 버전 GetOrAdd()
:
public static Task<T> GetOrAddAsync<T>(this IMemoryCache cache, string key, int seconds, Func<Task<T>> taskFactory)
{
return cache.GetOrCreateAsync<T>(key, async entry => await new AsyncLazy<T>(async () =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(seconds);
return await taskFactory.Invoke();
}).Value);
}
마지막으로 다음을 호출합니다.
IMemoryCache cache;
var result = await cache.GetOrAddAsync("someKey", 60, async () => new object());
이 링크를 확인하십시오 : http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx
페이지 맨 아래로 이동하거나 "스레드 안전"텍스트를 검색하십시오.
다음이 표시됩니다.
^ 스레드 안전
이 유형은 스레드로부터 안전합니다.
.Net 2.0 문제를 해결하기 위해 샘플 라이브러리를 업로드했습니다.
이 저장소를 살펴보십시오.
Redis 캐시를 사용하고 있지만 Connectionstring이없는 경우 장애 조치 또는 Memorycache도 사용합니다.
LazyCache 라이브러리를 기반으로 콜백을 실행하는 데 매우 비용이 많이 드는 경우 특별히 데이터를로드하고 저장하려는 다중 스레딩 이벤트에서 쓰기위한 콜백의 단일 실행을 보장합니다.
@pimbrouwers의 답변에서 @AmitE가 언급했듯이 그의 예제는 여기에 설명 된대로 작동하지 않습니다.
class Program
{
static async Task Main(string[] args)
{
var cache = new MemoryCache(new MemoryCacheOptions());
var tasks = new List<Task>();
var counter = 0;
for (int i = 0; i < 10; i++)
{
var loc = i;
tasks.Add(Task.Run(() =>
{
var x = GetOrAdd(cache, "test", TimeSpan.FromMinutes(1), () => Interlocked.Increment(ref counter));
Console.WriteLine($"Interation {loc} got {x}");
}));
}
await Task.WhenAll(tasks);
Console.WriteLine("Total value creations: " + counter);
Console.ReadKey();
}
public static T GetOrAdd<T>(IMemoryCache cache, string key, TimeSpan expiration, Func<T> valueFactory)
{
return cache.GetOrCreate(key, entry =>
{
entry.SetSlidingExpiration(expiration);
return new Lazy<T>(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
}).Value;
}
}
산출:
Interation 6 got 8
Interation 7 got 6
Interation 2 got 3
Interation 3 got 2
Interation 4 got 10
Interation 8 got 9
Interation 5 got 4
Interation 9 got 1
Interation 1 got 5
Interation 0 got 7
Total value creations: 10
It seems like GetOrCreate
returns always the created entry. Luckily, that's very easy to fix:
public static T GetOrSetValueSafe<T>(IMemoryCache cache, string key, TimeSpan expiration,
Func<T> valueFactory)
{
if (cache.TryGetValue(key, out Lazy<T> cachedValue))
return cachedValue.Value;
cache.GetOrCreate(key, entry =>
{
entry.SetSlidingExpiration(expiration);
return new Lazy<T>(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
});
return cache.Get<Lazy<T>>(key).Value;
}
That works as expected:
Interation 4 got 1
Interation 9 got 1
Interation 1 got 1
Interation 8 got 1
Interation 0 got 1
Interation 6 got 1
Interation 7 got 1
Interation 2 got 1
Interation 5 got 1
Interation 3 got 1
Total value creations: 1
The cache is threadsafe, but like others have stated, its possible that GetOrAdd will call the func multiple types if call from multiple types.
Here is my minimal fix on that
private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1);
and
await _cacheLock.WaitAsync();
var data = await _cache.GetOrCreateAsync(key, entry => ...);
_cacheLock.Release();
참고URL : https://stackoverflow.com/questions/20149796/memorycache-thread-safety-is-locking-necessary
'IT TIP' 카테고리의 다른 글
REST API 승인 및 인증 (웹 + 모바일) (0) | 2020.11.04 |
---|---|
os.path.exists와 os.path.isdir의 장단점 (0) | 2020.11.04 |
Golang에서 파일을 복사하는 간단한 방법 (0) | 2020.11.04 |
Typescript 가져 오기 / as 대 가져 오기 / 필수? (0) | 2020.11.04 |
Java 프로그램을 디버깅하는 동안 Eclipse에서 일부 기능을 실행하는 방법은 무엇입니까? (0) | 2020.11.04 |