2011-11-02 3 views
0

항목이없는 경우 해당 항목을 요청한 한 사람 만이 항목을 생성하는 데 시간을 소비하며 동시에 다른 사용자가 요청하면 동시에 첫 번째 사람이 결과를 캐시 할 때까지 차단하십시오. 1이오고 스레드 1이 캐시에없는 것을보고 캐쉬 된 항목의 단일 작성을 보장하는 캐시

  • DateA
  • 의 캐시 된 데이터를 요청하고 정보를 생성하는 비교적 긴 프로세스를 시작

    • 스레드 : 여기에 그 시나리오에 대한 설명입니다 A는, 정보가 현재 발생되고 있음을보고 스레드 3도에 와서 정보가 현재 발생되고 있음을 볼 수
    • 잠금 어떤 종류의에 대기 잠금 어떤 종류의 대기에
    • 스레드 2는 오는
    • 스레드 4가 들어와 req 이미 캐시에있는 다른 키에 대한 데이터를 유실하고 전혀 기다리지 않습니다.
    • 스레드 1이 생성을 완료하고
    • 값으로 캐시를 업데이트합니다. 스레드 2와 3이 모두 일어나서 결과를 얻습니다. 이제 캐시에
    내가 ConcurrentDictionary<DateTime, CustomImmutableObject> _cache;

    같은 키처럼 날짜를 저장하는 날짜 시간으로 ConcurrentDictionary에 캐시있을 것입니다하지만 스레드 2, 3 것이 얼마나 내가 볼 수없는 생각

    뭔가를 기다릴 수 있습니다. 일종의 상태 플래그를 저장 한 다른 ConcurrentDictionary가 있더라도 스레드 1이 완료되면 어떻게 알 수 있습니까?

    누구나 그러한 캐시를 개발하는 방법에 대한 제안이 있습니까?

    답변

    4

    다음과 같이 객체를 얻기에 ConcurrentDictionary<DateTime, Lazy<CustomImmutableObject>>을 사용할 수

    myDictionary 
        .GetOrAdd(
         someDateTime, 
         dt => new Lazy<CustomImmutableObject>(
          () => CreateMyCustomImmutableObject() 
         ) 
        ).Value 
    

    그 초기화를 보장합니다 (기본값) 스레드 안전 모드에서 Lazy<T> 만든 보증 한 번만 발생 이후의 값이 이전에 액세스 실제로 인스턴스화하면 차단됩니다.

    다중 스레드 시나리오에서 스레드 안전 Lazy (Of T) 개체의 Value 속성에 액세스하는 첫 번째 스레드는 모든 스레드에서 모든 후속 액세스에 대해이를 초기화하고 모든 스레드가 동일한 데이터를 공유합니다. 따라서 스레드가 개체를 초기화하는 스레드가 중요하지 않으며 경쟁 조건이 양호합니다.

    자세한 내용은 here을 참조하십시오.

    다음은 내가 분출하지 않도록하기 위해 작성한 테스트입니다.

    void Main() 
    { 
        var now=DateTime.UtcNow; 
        var d=new ConcurrentDictionary<DateTime, Lazy<CustomImmutableObject>>(); 
        Action f=()=>{ 
         var val=d 
          .GetOrAdd(
           now, 
           dt => new Lazy<CustomImmutableObject>(
            () => new CustomImmutableObject() 
           ) 
          ).Value; 
         Console.WriteLine(val); 
        }; 
        for(int i=0;i<10;++i) 
        { 
         (new Thread(()=>f())).Start(); 
        } 
        Thread.Sleep(15000); 
        Console.WriteLine("Finished"); 
    } 
    
    class CustomImmutableObject 
    { 
        public CustomImmutableObject() 
        { 
         Console.WriteLine("CREATING"); 
         Thread.Sleep(10000); 
        } 
    } 
    
    +0

    감사합니다. 좋은 해결책 인 것 같습니다. relfector에서 Lazy 을 보는 것은 기본적으로 Tuple 이며 값이 아직 만들어지지 않은 경우 객체를 잠급니다. 비슷한 종류의 두 사전 (잠긴 개체와 캐시 값을 가진 개체)은 아래에 게시했지만 훨씬 더 깨끗합니다. – BrandonAGr

    +0

    참으로. 개인적으로 ConcurrentDictionary의 GetOrAdd 메서드에 적용된 동일한 접근 방식을 보았을 것이지만 동시 사용에서는 추가 대리자를 두 번 이상 호출 할 수 있으며 반환 값은 하나만 제외하고 모두 무시됩니다. 물론 Lazy를 사용하면이 문제를 완화 할 수 있습니다. – spender

    +1

    "ConcurrentDictionary의 GetOrAdd 메서드에 동일한 접근 방식을 적용한 것이 좋았을 것입니다."-이 접근 방식은 잠금이 유지되는 동안 추가 대리자에서 호출 된 임의의 외부 코드가 디버깅을 어렵게하는 원인이 될 수 있습니다 버그 (재 입력, 장기간 잠금 장치). IMHO는 현재의 디자인이 가장 좋은 절충안입니다. – Joe

    0

    누군가가 액세스 할 때마다 캐시 객체에 lock을 넣을 수 있으며 캐시 미스가있는 경우 객체 생성을 나타내는 Monitor.Enter을 수행 한 다음 첫 번째 잠금을 해제하십시오. 작성이 완료되면 두 번째 잠금 오브젝트에서 Monitor.Exit을 사용하십시오.

    캐시 액세스는 일반적으로 주 객체를 잠그지 만 생성은 두 번째 객체에서 잠급니다. 생성을 병렬로 수행하려면 사전에 하나의 잠금 객체를 만들 수 있습니다. 여기서 키는 캐시의 동일한 키입니다.

    0

    내가 작동하는 것 같다 다음과 같은 접근 방식 함께했다하지만, 모든에 대한 잠금의 비용으로 캐시를 읽어 : 이것은 모두가 좋은 당신을 설득해야한다. 한 번에 하나의 사람 만이 주어진 키에 대해 캐시 데이터에 추가 할 수 있다고 가정하는 것이 안전한가요?

    static ConcurrentDictionary<DateTime, object> cacheAccess = new ConcurrentDictionary<DateTime, object>(); 
    static ConcurrentDictionary<DateTime, int> cacheData = new ConcurrentDictionary<DateTime, int>(); 
    
    static int GetValue(DateTime key) 
    { 
        var accessLock = cacheAccess.GetOrAdd(key, x => new object()); 
    
        lock (accessLock) 
        { 
         int resultValue; 
         if (!cacheData.TryGetValue(key, out resultValue)) 
         { 
          Console.WriteLine("Generating {0}", key); 
          Thread.Sleep(5000); 
          resultValue = (int)DateTime.Now.Ticks; 
          if (!cacheData.TryAdd(key, resultValue)) 
          { 
           throw new InvalidOperationException("How can something else have added inside this lock?"); 
          } 
         } 
    
         return resultValue; 
        } 
    } 
    
    
    static void Main(string[] args) 
    { 
        var keys = new[]{ DateTime.Now.Date, DateTime.Now.Date.AddDays(-1), DateTime.Now.Date.AddDays(1), DateTime.Now.Date.AddDays(2)}; 
        var rand = new Random(); 
    
        Parallel.For(0, 1000, (index) => 
         { 
          var key = keys[rand.Next(keys.Length)]; 
    
          var value = GetValue(key); 
    
          Console.WriteLine("Got {0} for key {1}", value, key); 
         }); 
    } 
    
    0

    는 일반적으로이 같은 것을 함께 할 것입니다 :

    public static object GetItem(DateTime dt) 
    { 
        // Check to see if the cache already has my item 
        if (_dictionary.ContainsKey(dt)) 
         return _dictionary[dt]; 
    
        lock (_lock) 
        { 
         // Check the cache AGAIN in case a previous thread inserted our value 
         if (_dictionary.ContainsKey(dt)) 
          return _dictionary[dt]; 
    
         // Add the generate object to the dictionary 
         _dictionary.Add(dt, GenerateMyObject(dt)); 
        } 
    } 
    private static Dictionary<DateTime, object> _dictionary = new Dictionary<DateTime, object>(); 
    private static object _lock = new object(); 
    

    을만큼 당신이 당신이 괜찮을 것 같네요 잠금 내부에서 찾고있는 어떤 개체에 대한 캐시를 다시 확인한다.

    관련 문제