평행 한 내부에 해시 테이블을 사용 하시겠습니까?
-
11-09-2019 - |
문제
본체 내부에서 집중적 인 작동을 실행하는 oreareach 루프가 있습니다.
작업은 해시 가능을 사용하여 값을 저장할 수 있으며 다른 연속 루프 항목에 대해 재사용 할 수 있습니다. 집중 작업이 완료된 후 해시 테이블에 추가하면 다음 루프 항목이 집중 작업을 다시 실행하는 대신 해시 테이블을 찾아 객체를 재사용 할 수 있습니다.
그러나 Paremal.Fereach를 사용하고 있기 때문에 안전하지 않은 문제가 있으므로 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
}
이 시나리오를 처리 할 수있는 TPL 라이브러리 내부에는 API, 속성 설정이 있어야합니다. 거기가 있습니까?
해결책
당신은 찾고 있습니다 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
모두 독특하지는 않습니다 item.Key
, 그 다음에 SomeIntensiveOperation
그 키를 위해 두 번 호출 될 수 있습니다. 예에서는 키가 전달되지 않습니다 SomeIntensiveOperation
, 그러나 "값으로 무언가를하는"코드가 키/valuea 및 key/valueb 쌍을 실행할 수 있으며, 한 가지 결과 만 캐시에 저장 될 수 있음을 의미합니다 (반드시 중단으로 계산 된 첫 번째 결과는 아닙니다). 이것을 처리하려면 평행 게으른 공장이 필요합니다 만약에 문제입니다. 또한 명백한 이유로 약간의 집중화는 스레드 안전해야합니다.
다른 팁
~을 체크하다 System.collections.concurrent 네임 스페이스 당신이 필요하다고 생각합니다 동시 소설
ReaderWriterLock을 사용하십시오. 이것은 짧은 기간 동안 많은 읽기와 글을 많이 가진 작업에 좋은 성능을 가지고 있습니다. 귀하의 문제는이 사양에 맞는 것 같습니다.
모든 읽기 작업은 빠르게 실행되고 잠글 수 있습니다. 누군가가 차단할 때는 쓰기가 발생할 때 뿐이며, 그 글은 해시 가능에 무언가를 밀어내는 데 걸리는 한 글입니다.
코드를 버릴 것 같아요 ...
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;
}
나는 (다소 명백한) 잠금을 사용하는 것 외에 다른 올바른 선택을 보지 못합니다 (동기화 된 해시 테이블은 모든 메소드를 잠금으로 무시합니다).
또 다른 옵션은 사전이 동기화되지 않도록하는 것입니다. 레이스 조건은 사전을 손상시키지 않으며, 단지 불필요한 계산을 수행하기 위해 코드 만 있으면됩니다. 자물쇠 또는 누락 된 메모 화가 더 나쁜 영향을 미치는지 확인하기 위해 코드를 프로파일 링하십시오.