2010-11-20 2 views
3

안전하게 업데이트하려는 정적 사전이 있습니다. 초기에는 사전이 비어 있지만 앱이 끝나면 새로운 값이 추가됩니다. 또한 정수 값은 증가 및 감소 할 수있는 개별 카운터로 작동합니다..NET4.0 : 사전 및 그 값을 업데이트하는 스레드 안전 방식

private static Dictionary<string, int> foo = new Dictionary<string, int>(); 

public static void Add(string bar) 
{ 
    if (!foo.ContainsKey(bar)) 
     foo.Add(bar, 0); 

    foo[bar] = foo[bar] + 1; 
} 


public static void Remove(string bar) 
{ 
    if (foo.ContainsKey(bar)) 
    { 
     if (foo[bar] > 0) 
      foo[bar] = foo[bar] - 1; 
    } 
} 

나는 스레드 안전성을 제공하는 수단으로 Interlocked class에 독서 있었고, 내가 사용할 수있는 것이 될 수 있다는 것을 나타납니다 그러나

public static void Add(string bar) 
{ 
    if (!foo.ContainsKey(bar)) 
     foo.Add(bar, 0); 

    Interlocked.Increment(ref foo[bar]); 
} 


public static void Remove(string bar) 
{ 
    if (foo.ContainsKey(bar)) 
    { 
     if (foo[bar] > 0) 
      Interlocked.Decrement(ref foo[bar]); 
    } 
} 

, 내 직감 본능 사전에 새 항목을 실제로 추가 할 때 문제가 해결되지 않으므로 잠금 기능이 즉시 적용됩니다.

private static Dictionary<string, int> foo = new Dictionary<string, int>(); 
private static object myLock = new object(); 

public static void Add(string bar) 
{ 
    lock(myLock) 
    { 
     if (!foo.ContainsKey(bar)) 
      foo.Add(bar, 0); 

     Interlocked.Increment(ref foo[bar]); 
    } 
} 


public static void Remove(string bar) 
{ 
    lock(myLock) 
    { 
     if (foo.ContainsKey(bar)) 
     { 
      if (foo[bar] > 0) 
       Interlocked.Decrement(ref foo[bar]); 
     } 
    } 
} 

이 방법이 이상적입니까, 아니면 정확합니까? 그것을 향상시킬 수 있습니까? 나는 벗어 났습니까?

+0

Interlocked.Increment (ref foo [bar])를 사용하는 것이 가능합니다. 인덱서 또는 속성에서 작동하지 않는다는 오류 메시지가 컴파일되지 않습니까? –

답변

3

lock이 좋지만 (필요한 경우, Interlocked으로 충분하지 않을 수도 있음) 한 번 수행하면 Interlocked.IncrementInterlocked.Decrement은 필요하지 않습니다. 여러 스레드에서 Dictionary<TKey, TValue>에 액세스 할 때 발생하는 문제는 한 스레드가 내부 해시 테이블의 재 작성을 트리거 할 수 있다는 것이고, 그 스레드는 내부 구조에 대한 사전을 혼란에 빠뜨리는 사전에 추가되는 다른 스레드에 대한 중간 재 구축을 스왑 아웃합니다. 사전.

또한 this이 아닌 개체에 lock이 구현되어있어 구현이 좋습니다. chibacity이 지적했듯이이 lock 개체는 readonly이어야합니다.

멀티 스레드 시나리오에서 사전 방탄을 만들었다 고 생각하지 않도록 조심하십시오. 예를 들어, 다음과 같은 상황이 일어날 수 :

스레드 1은, 사전에 문자열 "Hello, World!"를 조회 횟수 1를 다시받습니다.

실 1 실 2 호출이 0에 카운트를 설정 키 "Hello, World!"으로 제거 2.

스레드에 대한 스왑됩니다.

스레드 2는 이제 카운트가 1이지만 실제로 0이라고 생각 스레드 1

스레드 1으로 교체된다.

마지막으로 .NET 4.0에서는 ConcurrentDictionary<TKey, TValue>을 사용해야합니다. 이는 사전에 instance-methods를 호출 할 때 lock의 필요성을 효과적으로 제거하지만 위의 시나리오가 발생하는 것을 제거하지는 않습니다.

+0

. NET 4.0을 사용하고 있기 때문에 컨센서스는 내 사전을 ConcurrentDictionary로 바꾸어야하는 것으로 보입니다. 이것은 그 일을 할 것처럼 보입니다. 그러나 위의 시나리오는 어떻게 피할 수 있습니까? – Bullines

3

.Net 4.0을 사용하는 경우 System.Collections.Concurrent 네임 스페이스의 컬렉션 사용에 대해 생각할 수 있습니다 (예 : ConcurrentDictionary < TKey, TValue >).

+0

.Net 4.0을 사용하고 있습니다. ystem.Collections.Concurrent가 제공하는 것을보기 위해 살펴볼 것입니다. – Bullines

1

사전의 add \ remove 작업은 스레드로부터 안전해야합니다. 이는 하나의 스레드에서 사전을 열거하면 다른 스레드가이를 수정하지 않기 때문입니다.

지우기, 삭제 등

이 SO 게시물에 대한 참조를 사용하면 개인 IDictionary < 내부에서 데이터를 유지하고 사전에 대한 래퍼를 만들어야합니다 스레드 안전 사전을 만들> 및 추가 등의 방법으로 잠금을 사용하려면 그 방법에 대해 - What's the best way of implementing a thread-safe Dictionary?

또는 .Net 4를 사용하는 경우 상자에서 나온 스레드 안전성을 제공하는 ConcurrentDictionary을 사용할 수 있습니다.

3

잠금을 사용하여 추가 및 제거 메소드에서 사전에 대한 다중 스레드 액세스를 보호 했으므로이 코드가 해당 잠금 범위 내에 있기 때문에 인터록 된 명령문은 필요하지 않습니다. 잠금 블록 내의 모든 코드는 이제 단일 스레드이므로 안전합니다.

작은 점이지만, 현재 myLock 객체를 다시 할당하여 변경할 수 있으므로 myLock 객체를 읽기 전용으로 표시해야합니다. 따라서 한 스레드가 스레드를 잠근 상태에서 개체를 변경할 수 있으며 후속 스레드는 다른 잠금 개체를보고 이전 스레드 잠금을 무시할 수 있습니다. 객체를 읽기 전용으로 표시하면 변경할 수 없습니다.

+1

나는 lock 객체를 읽기 전용으로 만드는 아이디어를 좋아한다. :) – Bullines

0

Monitor.Enter(), Monitor.Exit() (lock (object))가 가장 좋은 방법 일 것입니다. 하지만이 코드는 다소 개선 될 수 있지만 실제 이점은 중요하지 않을 수 있습니다. 제거 위해 동일을 할 것

 private static Dictionary<string, int> foo = new Dictionary<string, int>(); 
    private static ReaderWriterLock rwLock= new ReaderWriterLock(); 
    static int reads = 0; 
    static int writes = 0; 


    private static bool Contains(string bar) 
    { 
      try 
      { 
       rwLock.AquireReaderLock(TimeOut.Infinite); 
       InterLocked.Increment(ref reads); 
       return foo.ContainsKey(bar); 


      } 
      catch(Exception) 
      { 


      } 
      finally 
      { 
       rwLock.ReleaseReaderLock(); 
      } 
    } 

    public static void Add(string bar) 
    { 
     try 
     { 
      rwLock.AquireWriterLock(TimeOut.Infinite); 

      if (!ContainsKey(bar)) 
      { 
       foo.Add(bar, 0); 
      } 
      foo[bar] = foo[bar] + 1; 
      Interlocked.Increment(ref writes); 
     } 
     catch(Exception) {} 
     finally 
     { 
       rwLock.ReleaseWriterLock(); 
     } 

}.