MemoryCache थ्रेड सुरक्षा, लॉकिंग आवश्यक है?


91

शुरुआत के लिए मुझे बस इसे वहीं फेंक देना चाहिए, मुझे पता है कि नीचे दिया गया कोड थ्रेड सुरक्षित नहीं है (सुधार: हो सकता है)। मैं जिस चीज से जूझ रहा हूं वह एक ऐसा कार्यान्वयन है जो एक ऐसा है जिसे मैं वास्तव में परीक्षण के तहत विफल कर सकता हूं। मैं अभी एक बड़ी WCF परियोजना को फिर से शुरू कर रहा हूं जिसमें कुछ (ज्यादातर) स्थिर डेटा की आवश्यकता है और यह एक SQL डेटाबेस से आबाद है। इसे समाप्त करने और दिन में कम से कम एक बार "ताज़ा" करने की आवश्यकता है, यही कारण है कि मैं मेमोरीकैच का उपयोग कर रहा हूं।

मुझे पता है कि नीचे दिया गया कोड थ्रेड सुरक्षित नहीं होना चाहिए, लेकिन मैं इसे भारी लोड के तहत विफल करने के लिए नहीं प्राप्त कर सकता हूं और मामलों को जटिल करने के लिए एक 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;
    }
}

ObjectCache थ्रेड सुरक्षित है, मुझे नहीं लगता कि आपकी कक्षा विफल हो सकती है। msdn.microsoft.com/en-us/library/… आप एक ही समय में डेटाबेस में जा सकते हैं लेकिन यह केवल आवश्यकता से अधिक सीपीयू का उपयोग करेगा।
the_lotus

1
जबकि ObjectCache थ्रेड सुरक्षित है, लेकिन इसे लागू नहीं किया जा सकता है। इस प्रकार MemoryCache प्रश्न।
हनी

जवाबों:


78

डिफ़ॉल्ट एमएस-प्रदान MemoryCacheपूरी तरह से सुरक्षित धागा है। MemoryCacheहो सकता है कि कोई भी कस्टम कार्यान्वयन थ्रेड सुरक्षित न हो। यदि आप MemoryCacheबॉक्स से बाहर सादे का उपयोग कर रहे हैं , तो यह थ्रेड सुरक्षित है। मेरे खुले स्रोत के सोर्स कोड वितरित कैशिंग समाधान को देखें कि मैं इसका उपयोग कैसे करता हूं (MemCache.cs)।

https://github.com/haneytron/dache/blob/master/Dache.CacheHost/Storage/MemCache.cs


2
डेविड, बस पुष्टि करने के लिए, मेरे पास बहुत ही सरल उदाहरण वर्ग में, कॉल .Remove () वास्तव में थ्रेड सुरक्षित है यदि एक अलग थ्रेड कॉल करने की प्रक्रिया में है Get ()? मुझे लगता है कि मुझे सिर्फ रिफ्लेक्टर का उपयोग करना चाहिए और गहरी खुदाई करनी चाहिए, लेकिन वहाँ बहुत सारी परस्पर विरोधी जानकारी है।
जेम्स लेगन

12
यह थ्रेड सुरक्षित है, लेकिन दौड़ की स्थिति के लिए प्रवण ... यदि आपका गेट आपके निकालें से पहले होता है, तो डेटा गेट पर वापस आ जाएगा। यदि पहले निकालें, तो यह नहीं होगा। यह एक डेटाबेस पर गंदे रीड्स की तरह है।
हैनी

10
उल्लेख करने के लिए लायक है (जैसा कि मैं नीचे एक और जवाब में टिप्पणी की), डॉटनैट कोर कार्यान्वयन वर्तमान में है नहीं पूरी तरह से सुरक्षित थ्रेड। विशेष रूप से GetOrCreateविधि। वहाँ एक मुद्दा है कि github पर
अमित

क्या कंसोल का उपयोग करके थ्रेड्स चलाते समय मेमोरी कैश एक अपवाद "मेमोरी से बाहर" फेंक देता है?
फसीह हरिस

49

जबकि MemoryCache वास्तव में थ्रेड सुरक्षित है जैसा कि अन्य उत्तरों ने निर्दिष्ट किया है, इसमें एक सामान्य मल्टी थ्रेडिंग मुद्दा है - यदि 2 थ्रेड्स एक ही समय में कैश Getसे (या चेक Contains) करने की कोशिश करते हैं , तो दोनों कैश को याद करेंगे और दोनों ही समाप्त हो जाएंगे। परिणाम और फिर दोनों परिणाम को कैश में जोड़ देंगे।

अक्सर यह अवांछनीय है - दूसरे धागे को पहले परिणाम के लिए इंतजार करना चाहिए और दो बार परिणाम उत्पन्न करने के बजाय इसके परिणाम का उपयोग करना चाहिए।

यह उन कारणों में से एक था, जिन्हें मैंने LazyCache लिखा था - MemoryCache पर एक अनुकूल आवरण जो इस प्रकार के मुद्दों को हल करता है। यह नुगेट पर भी उपलब्ध है ।


"इसमें एक सामान्य बहु सूत्रण मुद्दा है" यही कारण है कि आपको AddOrGetExistingआसपास के कस्टम लॉजिक को लागू करने के बजाय परमाणु विधियों का उपयोग करना चाहिए ContainsAddOrGetExistingMemoryCache की विधि परमाणु और थ्रेडसेफ़ संदर्भ
एलेक्स

1
हां AddOrGetExisting थ्रेड सुरक्षित है। लेकिन यह मानता है कि आपके पास पहले ही ऑब्जेक्ट का संदर्भ है जो कैश में जोड़ा जाएगा। आम तौर पर आप AddOrGetExisting नहीं चाहते हैं कि आप "GetExistingOrGenerateThisAndCacheIt" चाहते हैं जो कि LazyCache आपको देता है।
alastairtree

हाँ, "यदि आपके पास पहले से ही आज्ञाकारी नहीं है" बिंदु पर सहमत है
एलेक्स

17

जैसा कि दूसरों ने कहा है, मेमोरीकैच वास्तव में धागा सुरक्षित है। हालाँकि, इसके भीतर संग्रहीत डेटा की थ्रेड सुरक्षा पूरी तरह से आपके उपयोग की है।

संक्षिप्तता और प्रकार के बारे में अपने भयानक पोस्ट से रीड कोपसे को उद्धृत करने के लिए 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());

यह सब अतुल्यकालिक रूप से करने के लिए, मैं स्टीफन टूब के एमएसडीएन पर AsyncLazy<T>उनके लेख में पाए गए उत्कृष्ट कार्यान्वयन का उपयोग करने की सलाह देता हूं । जो 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())
    { }
}   

अब का Async संस्करण 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());

2
मैंने इसकी कोशिश की, और यह काम नहीं करता है (डॉट नेट कोर 2.0)। प्रत्येक GetOrCreate एक नया आलसी उदाहरण बनाएगा, और कैश को नए आलसी के साथ अद्यतन किया जाता है, और इसलिए, मान कई बार (एक एकाधिक थ्रेड एनवी में) का मूल्यांकन किया जाता है।
अमिते

2
इसके लुक से .netcore 2.0 MemoryCache.GetOrCreateउसी तरह ConcurrentDictionaryसे सुरक्षित नहीं है जैसे कि
अमित

शायद एक मूर्खतापूर्ण सवाल है, लेकिन क्या आप निश्चित हैं कि यह मूल्य कई बार बनाया गया था और नहीं Lazy? यदि हां, तो आपने इसे कैसे मान्य किया?
पीआईएम

3
मैंने एक फैक्ट्री फ़ंक्शन का उपयोग किया था जो स्क्रीन पर प्रिंट करता है जब इसका उपयोग किया गया था + एक यादृच्छिक संख्या उत्पन्न करता है, और 10 थ्रेड्स GetOrCreateको एक ही कुंजी और उस कारखाने का उपयोग करने की कोशिश करते हुए शुरू करते हैं। परिणाम, मेमोरी कैश (प्रिंट्स देखा) के साथ उपयोग करते समय कारखाने का 10 बार उपयोग किया गया था + हर बार GetOrCreateएक अलग मूल्य लौटाया गया! मैं एक ही परीक्षण का उपयोग करके चलाता हूं ConcurrentDicionaryऔर केवल एक बार उपयोग किए जा रहे कारखाने को देखा, और हमेशा एक ही मूल्य प्राप्त किया। मुझे
गितुब

चेतावनी: यह उत्तर काम नहीं कर रहा है: आलसी <टी> कैश्ड फ़ंक्शन के कई निष्पादन को रोक नहीं रहा है। @Snicker उत्तर देखें। [नेट कोर २.०]
फर्नांडो सिल्वा

11

इस लिंक को देखें: http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx

पृष्ठ के बहुत नीचे जाएं (या "थ्रेड सेफ्टी" टेक्स्ट खोजें)।

तुम देखोगे:

^ थ्रेड सेफ्टी

यह प्रकार थ्रेड सुरक्षित है।


7
मैंने व्यक्तिगत अनुभव के आधार पर कुछ समय पहले MSDN की "थ्रेड सेफ" की परिभाषा पर भरोसा करना बंद कर दिया था। यहाँ एक अच्छा पढ़ा है: लिंक
जेम्स लेगन

3
वह पोस्ट मेरे द्वारा दिए गए लिंक से थोड़ी अलग है। भेद इस बात में बहुत महत्वपूर्ण है कि मैंने जो लिंक प्रदान किया था, वह थ्रेड सेफ्टी की घोषणा के लिए किसी भी तरह की पेशकश नहीं करता था। मेरे पास MemoryCache.Defaultबहुत अधिक मात्रा में (प्रति मिनट लाखों कैश हिट्स) का उपयोग करने का व्यक्तिगत अनुभव है, जिसमें अभी तक कोई समस्या नहीं है।
एकोस्तिकमार्टिन

मुझे लगता है कि उनका मतलब यह है कि पढ़ने और लिखने के संचालन परमाणु रूप से होते हैं। दुर्भाग्य से, थ्रेड ए वर्तमान मूल्य को पढ़ने की कोशिश करता है, यह हमेशा पूरा लेखन संचालन पढ़ता है, किसी भी थ्रेड द्वारा मेमोरी में डेटा लिखने के बीच में नहीं। जब थ्रेड एक ए की कोशिश करता है। मेमोरी में लिखने के लिए किसी भी थ्रेड द्वारा कोई हस्तक्षेप नहीं किया जा सकता है। मेरी समझ है लेकिन इसके बारे में कई प्रश्न / लेख हैं जो निम्नलिखित की तरह पूर्ण निष्कर्ष पर नहीं जाते हैं। stackoverflow.com/questions/3137931/msdn-what-is-thread-safety
erhan355

3

.Net 2.0 के लिए मुद्दे को संबोधित करने के लिए बस नमूना पुस्तकालय अपलोड किया गया।

इस रेपो पर एक नज़र डालें:

RedisLazyCache

मैं Redis कैश का उपयोग कर रहा हूँ, लेकिन यह भी विफलता या सिर्फ Memorycache अगर Connectionstring गायब है।

यह LazyCache लाइब्रेरी पर आधारित है जो मल्टीबैकरिंग की स्थिति में लिखने के लिए कॉलबैक के एकल निष्पादन की गारंटी देता है और डेटा को विशेष रूप से लोड करने और सहेजने की कोशिश करता है यदि कॉलबैक निष्पादित करने के लिए बहुत महंगा है।


कृपया जवाब केवल साझा करें, अन्य जानकारी टिप्पणी के रूप में साझा की जा सकती है
इलास्टिककोड

1
@WaelAbbas। मैंने कोशिश की लेकिन ऐसा लगता है कि मुझे पहले 50 प्रतिष्ठा की आवश्यकता है। : डी। हालाँकि, यह ओपी प्रश्न का सीधा उत्तर नहीं है (हाँ / नहीं द्वारा उत्तर दिया जा सकता है, कुछ स्पष्टीकरण के साथ क्यों), मेरा उत्तर बिना उत्तर के संभावित समाधान के लिए है।
फ्रांसिस मार्सिगन

1

जैसा कि @ एमिट द्वारा @ पाइम्ब्रूवर्स के उत्तर में उल्लेख किया गया है, उनका उदाहरण यहां प्रदर्शित नहीं किया गया है:

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

ऐसा प्रतीत होता है जैसे GetOrCreate रिटर्न हमेशा बनाई गई प्रविष्टि है। सौभाग्य से, यह तय करना बहुत आसान है:

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;
}

उम्मीद के मुताबिक काम करता है:

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

2
यह भी काम नहीं करता है। बस इसे कई बार कोशिश करते हैं और आप देखेंगे कि कभी कभी मान हमेशा 1. नहीं है
mlessard

1

कैश थ्रेडसेफ़ है, लेकिन जैसा कि अन्य लोगों ने कहा है, यह संभव है कि GetOrAdd कई प्रकारों से कॉल करने पर func कई प्रकारों को कॉल करेगा।

यहाँ उस पर मेरा न्यूनतम निर्धारण है

private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1);

तथा

await _cacheLock.WaitAsync();
var data = await _cache.GetOrCreateAsync(key, entry => ...);
_cacheLock.Release();

मुझे लगता है कि इसका अच्छा समाधान है, लेकिन अगर मेरे पास अलग-अलग कैश को बदलने के कई तरीके हैं, अगर मैं लॉक का उपयोग करता हूं तो वे बिना आवश्यकता के लॉक हो जाएंगे! इस मामले में हमारे पास एकाधिक _cacheLocks होना चाहिए, मुझे लगता है कि यह बेहतर होगा यदि कैशलॉक भी एक कुंजी हो सकता है!
डैनियल

इसे हल करने के कई तरीके हैं, एक यह है कि जेनरिक है अर्धवृत्त MyCache <T> के प्रति अद्वितीय होगा। फिर आप इसे AddSingleton (टाइपोफ़ (IMyCache <>), टाइपोफ़ (MyCache <>)) पंजीकृत कर सकते हैं;
एंडर्स

मैं शायद पूरे कैश को एक सिंगलटन बना सकता हूं जो अन्य क्षणिक प्रकारों को लागू करने की आवश्यकता होने पर परेशानी का कारण बन सकता है। तो शायद एक semaphore स्टोर ICacheLock <T> जो सिंगलटन है
Anders

इस संस्करण के साथ समस्या यह है कि यदि आपके पास एक ही समय में कैश करने के लिए 2 अलग-अलग चीजें हैं, तो आपको पहले एक के लिए इंतजार करना होगा ताकि आप दूसरे के लिए कैश की जांच कर सकें। यह उन दोनों के लिए अधिक कुशल है यदि दोनों चाबियाँ अलग हैं, तो कैश (और उत्पन्न) की जांच करने में सक्षम हैं। LazyCache, Lazy <T> और धारीदार लॉकिंग के संयोजन का उपयोग करता है ताकि आपके आइटम कैश जितनी जल्दी हो सके, और केवल एक बार प्रति कुंजी उत्पन्न हो। देखें github.com/alastairtree/lazycache
alastairtree
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.