2011-03-18 4 views
2

다음 클래스가 있습니다.C#에서 개체 목록을 포함하는 사전에 잠금을 사용하는 방법?

public static class HotspotsCache 
{ 
    private static Dictionary<short, List<HotSpot>> _companyHotspots = new Dictionary<int, List<HotSpot>>(); 
    private static object Lock = new object(); 


    public static List<HotSpot> GetCompanyHotspots(short companyId) 
    { 
    lock (Lock) 
    { 
     if (!_companyHotspots.ContainsKey(companyId)) 
     { 
     RefreshCompanyHotspotCache(companyId); 
     } 

     return _companyHotspots[companyId]; 
    } 
    } 

    private static void RefreshCompanyHotspotCache(short companyId) 
    { 
    .... 

    hotspots = ServiceProvider.Instance.GetService<HotspotsService>().GetHotSpots(..); 
    _companyHotspots.Add(companyId, hotspots); 

    .... 
    } 

문제는 RefreshCompanyHotspotCache 메서드에서 핫스팟을 가져 오는 작업에 많은 시간이 소요된다는 것입니다. 따라서 특정 CompanyId에 대해 하나의 스레드가 캐시 새로 고침을 수행하는 동안이 작업이 완료 될 때까지 다른 모든 스레드가 대기하고 있지만 목록이 이미로드 된 다른 회사 ID의 핫스팟 목록을 요청하는 스레드가있을 수 있습니다. 사전. 이 마지막 스레드를 잠그지 않았 으면합니다. 또한 캐시에 아직로드되지 않은 회사의 핫 스폿 목록을 요청하는 모든 스레드가 목록이 완전히 검색되어 사전에로드 될 때까지 대기하도록합니다.

특정 companyId (새로 고침이 수행되는)에 대한 캐시 읽기/쓰기 스레드 만 잠그고 다른 회사의 데이터를 요청하는 다른 스레드가 작업을 수행하도록 할 수 있습니까?

제 생각은 잠금 장치를 사용하고 배열하는 것이 었습니다.

lock (companyLocks[companyId]) 
{ 
... 
} 

하지만 아무 것도 해결하지 못했습니다. 한 회사를 다루는 스레드는 여전히 다른 회사의 캐시를 새로 고치는 스레드를 기다리고 있습니다.

+0

코드를 여기에 배치 했으므로 GetCompanyHotspots를 호출하면 명시 적으로 새로 고침됩니다. 캐시 된 데이터를 읽는 적이 있습니까? –

+0

여기가 캐시 된 데이터를 읽습니다. return _companyHotspots [companyId]; 매번 명시적인 새로 고침을 수행하지 않습니다. – lukdevil

+0

"if dictionary.containsKey (id) {refresh (id);} 사전 [id];을 (를) 반환했습니다." containsKey가 false를 돌려주는 경우, 리프레쉬되지 않지만, return 스테이트먼트는 null를 돌려줍니다. containsKey가 true를 돌려주는 경우, 명시적인 리프레쉬를 실시해, 새롭게 리프레쉬 된 값을 돌려줍니다. 새로 고침이없는 _companyHotspots는 어디에서 읽습니까? –

답변

0

독자는 ReaderWriterLockSlim을 들여다 보았습니까? 그렇게되면 필요할 때만 쓰기 권한을 얻을 수있는 곳에 세밀한 나뭇결 잠금 기능을 제공 할 수 있어야합니다.

당신이 알아야하는 또 다른 사항은 허위 공유입니다. 나는 잠금이 정확히 구현되는 방법을 모르지만 배열에있는 객체를 잠그면 메모리에서 서로 가깝게 묶여있을 수 있습니다. 동일한 캐시 라인에 배치 할 수 있으므로 잠금이 예상대로 작동하지 않을 수 있습니다. . 당신이 잠금 문 자세한 내용은 여기 의도 한 것보다 랩이 될 수

object l = companyLocks[companyId]; 
lock(l){ 

} 

에 마지막으로 코드를 변경하는 경우

또 다른 아이디어는 어떻게됩니까.

GJ

1

어떻게 당신은 단지 하나 개의 스레드를 고정하고, 다른 사람들이 이전 목록을 사용하는 반면, 해당 업데이트를하자에 대해?

private static Dictionary<short, List<HotSpot>> _companyHotspots = new Dictionary<short, List<HotSpot>>(); 
private static Dictionary<short, List<HotSpot>> _companyHotspotsOld = new Dictionary<short, List<HotSpot>>(); 
private static bool _hotspotsUpdating = false; 
private static object Lock = new object(); 

public static List<HotSpot> GetCompanyHotspots(short companyId) 
{ 
    if (!_hotspotsUpdating) 
    { 
     if (!_companyHotspots.ContainsKey(companyId)) 
     { 
      lock (Lock) 
      { 
       _hotspotsUpdating = true; 
       _companyHotspotsOld = _companyHotspots; 
       RefreshCompanyHotspotCache(companyId); 

       _hotspotsUpdating = false; 
       return _companyHotspots[companyId]; 
      } 
     } 
     else 
     { 
      return _companyHotspots[companyId]; 
     } 
    } 
    else 
    { 
     return _companyHotspotsOld[companyId]; 
    } 
} 
+0

이 경우 문제는 새로 고침이 수행되는 회사의 캐시에서 읽는 스레드가 null을 반환한다는 것입니다. 따라서 캐시에없는 회사를 다루는 첫 번째 스레드가 핫스팟을 가져 와서 캐시에 추가하고이 작업이 완료 될 때까지 기다리는 것이 필요합니다. 이 작업으로 다른 회사의 다른 스레드를 잠글 수 없습니다. – lukdevil

2

사용도 Snowbear에 의해 언급 한 두 번 확인 잠금 장치 -이 실제로하지 않을 때 코드가 잠금 방지 할을 수행해야합니다.

클라이언트 당 개별 잠금에 대한 생각으로 나는 사전 잠금을 사용했지만 이전에는이 ​​메커니즘을 사용했습니다. 나는 키에서 잠금 객체를 가져 오기위한 유틸리티 클래스를 만든 :

/// <summary> 
/// Provides a mechanism to lock based on a data item being retrieved 
/// </summary> 
/// <typeparam name="T">Type of the data being used as a key</typeparam> 
public class LockProvider<T> 
{ 
    private object _syncRoot = new object(); 
    private Dictionary<T, object> _lstLocks = new Dictionary<T, object>(); 

    /// <summary> 
    /// Gets an object suitable for locking the specified data item 
    /// </summary> 
    /// <param name="key">The data key</param> 
    /// <returns></returns> 
    public object GetLock(T key) 
    { 
     if (!_lstLocks.ContainsKey(key)) 
     { 
      lock (_syncRoot) 
      { 
       if (!_lstLocks.ContainsKey(key)) 
        _lstLocks.Add(key, new object()); 
      } 
     } 
     return _lstLocks[key]; 
    } 
} 

그래서 당신은 다음 .NET 4를 사용할 수 있다면 단순히

private static LockProvider<short> _clientLocks = new LockProvider<short>(); 
private static Dictionary<short, List<HotSpot>> _companyHotspots = new Dictionary<short, List<HotSpot>>(); 

    public static List<HotSpot> GetCompanyHotspots(short companyId) 
    { 
     if (!_companyHotspots.ContainsKey(companyId)) 
     { 
      lock (_clientLocks.GetLock(companyId)) 
      { 
       if (!_companyHotspots.ContainsKey(companyId)) 
       { 
        // Add item to _companyHotspots here... 
       } 
     } 
     return _companyHotspots[companyId]; 
    } 
0

... 다음과 같은 방법으로 이것을 사용 대답은 간단합니다 - 대신 ConcurrentDictionary<K,V>를 사용하고 당신을위한 동시성 세부 사항 후에 살펴 보자

public static class HotSpotsCache 
{ 
    private static readonly ConcurrentDictionary<short, List<HotSpot>> 
     _hotSpotsMap = new ConcurrentDictionary<short, List<HotSpot>>(); 

    public static List<HotSpot> GetCompanyHotSpots(short companyId) 
    { 
     return _hotSpotsMap.GetOrAdd(companyId, id => LoadHotSpots(id)); 
    } 

    private static List<HotSpot> LoadHotSpots(short companyId) 
    { 
     return ServiceProvider.Instance 
           .GetService<HotSpotsService>() 
           .GetHotSpots(/* ... */); 
    } 
} 

를 사용할 수없는 경우.4 NET 다음 몇 가지 더 세분화 된 잠금을 사용하여 당신의 생각은 좋은 하나입니다

public static class HotSpotsCache 
{ 
    private static readonly Dictionary<short, List<HotSpot>> 
     _hotSpotsMap = new Dictionary<short, List<HotSpot>(); 

    private static readonly object _bigLock = new object(); 
    private static readonly Dictionary<short, object> 
     _miniLocks = new Dictionary<short, object>(); 

    public static List<HotSpot> GetCompanyHotSpots(short companyId) 
    { 
     List<HotSpot> hotSpots; 
     object miniLock; 
     lock (_bigLock) 
     { 
      if (_hotSpotsMap.TryGetValue(companyId, out hotSpots)) 
       return hotSpots; 

      if (!_miniLocks.TryGetValue(companyId, out miniLock)) 
      { 
       miniLock = new object(); 
       _miniLocks.Add(companyId, miniLock); 
      } 
     } 
     lock (miniLock) 
     { 
      if (!_hotSpotsMap.TryGetValue(companyId, out hotSpots)) 
      { 
       hotSpots = LoadHotSpots(companyId); 
       lock (_bigLock) 
       { 
        _hotSpotsMap.Add(companyId, hotSpots); 
        _miniLocks.Remove(companyId); 
       } 
      } 
      return hotSpots; 
     } 
    } 

    private static List<HotSpot> LoadHotSpots(short companyId) 
    { 
     return ServiceProvider.Instance 
           .GetService<HotSpotsService>() 
           .GetHotSpots(/* ... */); 
    } 
} 
0

새로운 아이디어, 그들이 만들어지면 바로 목록을 잠금으로. 각 회사가 적어도 하나의 핫스팟있을 것이라는 점을 보장 할 수있는 경우

, 이렇게 :

public static class HotspotsCache 
{ 
    private static Dictionary<short, List<HotSpot>> _companyHotspots = new Dictionary<int, List<HotSpot>>(); 

    static HotspotsCache() 
    { 
     foreach(short companyId in allCompanies) 
     { 
      companyHotspots.Add(companyId, new List<HotSpot>()); 
     } 
    } 

    public static List<HotSpot> GetCompanyHotspots(short companyId) 
    { 
     List<HotSpots> result = _companyHotspots[companyId]; 

     if(result.Count == 0) 
     { 
      lock(result) 
      { 
       if(result.Count == 0) 
       { 
        RefreshCompanyHotspotCache(companyId, result); 
       } 
      } 
     } 

     return result; 
    } 

    private static void RefreshCompanyHotspotCache(short companyId, List<HotSpot> resultList) 
    { 
     .... 

     hotspots = ServiceProvider.Instance.GetService<HotspotsService>().GetHotSpots(..); 
     resultList.AddRange(hotspots); 

     .... 
    } 
} 

를 사전이 초기 생성 후 수정되고 있기 때문에, 필요 그것에 모든 잠금을 할 수 없습니다. 개별 목록을 채울 때마다 개별 목록을 잠그기 만하면됩니다. 읽기 작업에는 잠금이 필요하지 않습니다 (초기 Count == 0 포함).

+0

감사합니다. 나는이 해결책을 시도 할 것이다. 각 회사마다 최소한 하나의 핫스팟이 있다고 보장 할 수는 없지만 모든 회사의 목록에 빈 핫스팟을 추가하면 영향을받지 않습니다. 나는이 해결책이 효과가있을 것이라고 생각한다. – lukdevil

+0

빈 목록으로 사전을 미리 채우는 경우 GetCompanyHotspots 메서드가로드해야하는 대상과 핫 스폿이없는로드 간의 차이를 알 수있는 방법이 없습니다. 이 경우 아마도 bool [65536] companiesLoaded를 포함하거나로드 된 항목을보다 명시 적으로 나타내야합니다. –

관련 문제