2009-11-01 2 views
7

본문 내부에서 집중적 인 작업을 실행하는 Parallel.ForEach 루프가 있습니다.Parallel.ForEach 안에 해시 테이블을 사용하고 있습니까?

조작은 Hashtable을 사용하여 값을 저장하고 다른 연속 루프 항목에 대해 재사용 할 수 있습니다. 집중적 인 작업이 완료된 후 Hashtable에 추가하면 다음 루프 항목이 집중적 인 작업을 다시 실행하는 대신 Hashtable을 검색하여 개체를 다시 사용할 수 있습니다.

그러나 Parallel.ForEach를 사용하기 때문에 Hashtable.Add 및 ContainsKey (키) 호출이 동기화되지 않아 안전하지 않은 문제가 발생합니다. 이러한 호출은 병렬로 실행될 수 있으므로 동기화되지 않습니다. 잠금 장치를 도입하면 성능 문제가 발생할 수 있습니다.

다음은 샘플 코드입니다 :

Hashtable myTable = new Hashtable; 
Parallel.ForEach(items, (item, loopState) => 
{ 
    // If exists in myTable use it, else add to hashtable 
    if(myTable.ContainsKey(item.Key)) 
    { 
     myObj = myTable[item.Key]; 
    } 
    else 
    { 
     myObj = SomeIntensiveOperation(); 
     myTable.Add(item.Key, myObj); // Issue is here : breaks with exc during runtime 
    } 
    // Do something with myObj 
    // some code here 
} 

일부 API, TPL 라이브러리 내부의 속성 설정, 즉이 시나리오를 처리 할 수 ​​있어야합니다. 거기 있니?

답변

18

당신은 System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>을 찾고 있습니다. 새로운 동시 수집은 크게 향상된 잠금 메커니즘을 사용하며 병렬 알고리즘에서 탁월하게 수행해야합니다.

편집 : 결과는 다음과 같습니다 경고의

ConcurrentDictionary<T,K> cache = ...; 
Parallel.ForEach(items, (item, loopState) => 
{ 
    K value; 
    if (!cache.TryGetValue(item.Key, out value)) 
    { 
     value = SomeIntensiveOperation(); 
     cache.TryAdd(item.Key, value); 
    } 

    // Do something with value 
}); 

말씀 :items의 요소가 할 경우는 모든는 SomeIntensiveOperation 해당 키를 두 번 전화를받을 수있는, 독특한 item.Key 있습니다. 이 예제에서 키는 SomeIntensiveOperation으로 전달되지 않지만 "값을 가지고 수행하십시오"코드가 key/valueA 및 key/valueB 쌍을 실행할 수 있으며 하나의 결과 만 캐시에 저장됩니다 (반드시 첫 번째 것은 SomeIntensiveOperation에 의해 계산 됨). 만약이 문제라면, 이것을 처리하기 위해 병렬 게으른 공장이 필요합니다. 또한 SomeIntensiveOperation이 스레드로부터 안전해야한다는 분명한 이유가 있습니다.

+1

@AdamRalph 필요하다고 생각 : 그는 TPL 라이브러리 그는를 사용하기 때문에 이미 .net 4를 사용 중입니다.0 –

+0

@Adam & Yassir : 정확합니다. 새 컬렉션은 Parallel LINQ를 염두에두고 설계되었습니다. –

+0

답변과 의견을 보내 주셔서 감사합니다 – Vin

1

(다소 외설적 인) 잠금을 사용하는 것보다 다른 올바른 선택을하지 않습니다 (동기화 된 Hashtable은 잠금이있는 모든 메소드를 재정의합니다).

또 다른 옵션은 사전이 동기화되지 않도록 할 수 있습니다. 경쟁 조건은 사전을 손상시키지 않으며, 단지 불필요한 계산을 수행하도록 코드를 요구할 것입니다. 잠금 또는 누락 된 메모가 더 나쁜 영향을 미치는지 여부를 확인하기 위해 코드를 프로파일하십시오.

3

ReaderWriterLock을 사용하면 읽기 작업이 많고 쓰기 작업이 짧은 작업에 좋은 성능을 발휘합니다. 귀하의 문제는이 사양에 맞는 것 같습니다.

모든 읽기 작업이 빠르게 실행되고 잠금이 해제됩니다. 누군가가 차단 될 수있는 유일한 시간은 쓰기가 발생하는 경우이며, 쓰기 작업은 Hashtable에서 임의의 작업을 수행하는 데 걸리는 시간만큼입니다. 내가 몇 가지 코드를 던져 것 같아요

ReaderWriterLockSlim on MSDN

...

ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); 
Hashtable myTable = new Hashtable(); 
Parallel.ForEach(items, (item, loopState) => 
{ 
    cacheLock.EnterReadLock(); 
    MyObject myObj = myTable.TryGet(item.Key); 
    cacheLock.ExitReadLock(); 

    // If the object isn't cached, calculate it and cache it 
    if(myObj == null) 
    { 
     myObj = SomeIntensiveOperation(); 
     cacheLock.EnterWriteLock(); 
     try 
     { 
      myTable.Add(item.Key, myObj); 
     } 
     finally 
     { 
      cacheLock.ExitWriteLock(); 
     }   
    } 
    // Do something with myObj 
    // some code here 
} 

static object TryGet(this Hashtable table, object key) 
{ 
    if(table.Contains(key)) 
     return table[key] 
    else 
     return null; 
} 
+0

".NET Framework에는 ReaderWriterLockSlim과 ReaderWriterLock이라는 두 개의 리더기 잠금 장치가 있습니다 .ReadWriterLockSlim은 모든 새로운 개발에 권장됩니다 .ReadWriterLockSlim은 ReaderWriterLock과 유사하지만 재귀 및 업그레이드를위한 규칙이 단순화되어 있습니다. ReaderWriterLockSlim은 많은 잠재적 인 교착 상태를 피할뿐만 아니라 ReaderWriterLockSlim의 성능이 ReaderWriterLock보다 훨씬 뛰어납니다. " –

+0

그 충고는 소리가 나지 않아 내 대답이 업데이트되었습니다. 관심이있는 분들은이 MSDN 잡지 기사를 살펴보십시오. http://msdn2.microsoft.com/en-us/magazine/cc163599.aspx – joshperry

관련 문제