ConcurrentDictionary
유형 에 대한 충분한 정보를 찾을 수 없으므로 여기에 대해 물어볼 것이라고 생각했습니다.
현재 a Dictionary
를 사용하여 여러 스레드 (스레드 풀에서 정확한 양의 스레드가 아닌)로 지속적으로 액세스하는 모든 사용자를 보유하고 있으며 액세스를 동기화했습니다.
최근에 .NET 4.0에 스레드 안전 컬렉션이 있다는 것을 알았으며 매우 기쁘게 생각합니다. Dictionary
동기화 된 액세스 가있는 일반 옵션을 사용 하거나 ConcurrentDictionary
이미 스레드로부터 안전한 옵션을 사용할 수 있기 때문에 ‘보다 효율적이고 관리하기 쉬운’옵션이 무엇인지 궁금 합니다.
답변
스레드 세이프 콜렉션과 비스 레드 세이프 콜렉션은 다른 방식으로 볼 수 있습니다.
계산대를 제외하고 점원이없는 상점을 고려하십시오. 사람들이 책임감있게 행동하지 않으면 많은 문제가 있습니다. 예를 들어, 고객이 피라미드 캔에서 캔을 가져 가고 있지만 점원이 현재 피라미드를 짓고 있다면 모든 것이 망가질 것입니다. 또는 두 명의 고객이 동시에 같은 상품을 구매하면 누가 이기는가? 싸움이 있을까요? 이것은 스레드로부터 안전하지 않은 콜렉션입니다. 문제를 피할 수있는 방법은 많이 있지만, 모두 일종의 잠금 또는 어떤 방식 으로든 명시적인 액세스가 필요합니다.
반면에 책상에 점원이있는 상점을 생각해보십시오. 상점을 통해서만 쇼핑 할 수 있습니다. 당신은 줄을 서서 그에게 물건을 요구하고, 그는 그것을 당신에게 가져오고, 당신은 줄을 벗어납니다. 여러 개의 물품이 필요한 경우, 각 왕복에서 기억할 수있는만큼만 물품을 수령 할 수 있지만, 점원의 호그를 피하지 않도록주의해야합니다. 이렇게하면 다른 고객이 뒤에서 줄을 서게됩니다.
이제 이것을 고려하십시오. 점원 한 명이있는 상점에서 줄 앞에 줄을 서서 점원에게 “화장지가 있습니까?”라고 물으면 “예”라고 말한 다음 “좋아요.” “내가 얼마나 필요한지 알면 다시 연락 할 것입니다.”그런 다음 줄 앞에서 돌아올 때마다 매장을 매진 할 수 있습니다. 이 시나리오는 스레드 세이프 컬렉션에 의해 방지되지 않습니다.
스레드 세이프 컬렉션은 내부 데이터 구조가 여러 스레드에서 액세스 된 경우에도 항상 유효한지 확인합니다.
스레드로부터 안전하지 않은 콜렉션에는 그러한 보장이 제공되지 않습니다. 예를 들어, 하나의 스레드에서 이진 트리에 무언가를 추가하고 다른 스레드가 트리를 재조정하는 데 바쁘지만 항목이 추가되거나 그 후에도 여전히 트리가 유효하다는 보장은 없습니다. 희망을 넘어서 손상 될 수 있습니다.
그러나 스레드 세이프 컬렉션은 스레드에 대한 순차적 작업이 모두 내부 데이터 구조의 동일한 “스냅 샷”에서 작동한다는 것을 보증하지 않습니다. 이는 다음과 같은 코드가있는 경우를 의미합니다.
if (tree.Count > 0)
Debug.WriteLine(tree.First().ToString());
당신은 inbetween 때문에 NullReferenceException이를 얻을 수 있습니다 tree.Count
및 tree.First()
다른 스레드 수단이 나무의 나머지 노드 없애 게했다, First()
반환됩니다 null
.
이 시나리오의 경우 해당 컬렉션에 원하는 것을 얻을 수있는 안전한 방법이 있는지 확인해야합니다. 위의 코드를 다시 작성해야하거나 잠 가야 할 수도 있습니다.
답변
thread-safe가 쓰레드 문제를 무시할 수 있다는 의미는 아니기 때문에 thread-safe 컬렉션을 사용할 때는 여전히주의해야합니다. 컬렉션이 스레드로부터 안전하다고 광고하면 일반적으로 여러 스레드가 동시에 읽고 쓰는 경우에도 일관성있는 상태로 유지됩니다. 그러나 이것이 단일 스레드가 여러 메소드를 호출하는 경우 “논리적”결과 시퀀스를 보게된다는 의미는 아닙니다.
예를 들어, 먼저 키가 있는지 확인한 다음 나중에 해당 키에 해당하는 값을 얻는 경우 다른 스레드가 키를 제거했을 수 있기 때문에 해당 키가 더 이상 ConcurrentDictionary 버전을 사용하더라도 존재하지 않을 수 있습니다. 이 경우에도 여전히 잠금을 사용해야합니다 (또는 더 나은 방법 : TryGetValue 를 사용하여 두 호출을 결합 ).
따라서 사용하지만 동시성 문제를 모두 무시할 수있는 무료 패스를 제공한다고 생각하지 마십시오. 여전히 조심해야합니다.
답변
내부적으로 ConcurrentDictionary는 각 해시 버킷에 대해 별도의 잠금을 사용합니다. 단일 항목에서 작동하는 Add / TryGetValue 및 이와 유사한 메소드 만 사용하는 한, 사전은 각각의 달콤한 성능 이점이있는 거의 잠금이없는 데이터 구조로 작동합니다. 열거 방법 (Count 속성 포함)은 모든 버킷을 한 번에 잠그므로 성능 측면에서 동기화 된 사전보다 더 나쁩니다.
나는 ConcurrentDictionary를 사용한다고 말하고 싶습니다.
답변
ConcurrentDictionary.GetOrAdd 메서드는 대부분의 멀티 스레드 시나리오에 필요한 것입니다.
답변
.Net 3.5sp1 용 Reactive Extensions 를 보셨습니까? Jon Skeet에 따르면 이들은 .Net3.5 sp1에 대한 병렬 확장 및 동시 데이터 구조 번들을 백 포트했습니다.
.Net 4 Beta 2에 대한 샘플 세트가 있는데, 이는 병렬 확장을 사용하는 방법에 대해 아주 자세하게 설명합니다.
방금 지난 주에 32 개의 스레드를 사용하여 I / O를 수행하는 ConcurrentDictionary를 테스트했습니다. 그것은 광고 된대로 작동하는 것 같습니다. 엄청난 양의 테스트가 이루어 졌음을 나타냅니다.
편집 : .NET 4 동시 사전 및 패턴.
Microsoft는 Patterns of Paralell Programming이라는 PDF를 출시했습니다. .Net 4 Concurrent 확장에 사용하기에 적합한 패턴과 피해야 할 안티 패턴에 대해 자세히 설명 된대로 다운로드 할 가치가 있습니다.여기있어.
답변
기본적으로 새로운 ConcurrentDictionary와 함께 가고 싶습니다. 스레드로부터 안전한 프로그램을 만들려면 코드를 적게 작성해야합니다.
답변
는 ConcurrentDictionary
그것을 충족하는 경우 최고의 선택이 될 것입니다 모든 스레드 안전 요구 사항을. 그렇지 않은 경우, 다시 말해서 약간 복잡한 작업을 수행하는 경우 일반 Dictionary
+ lock
가 더 나은 옵션 일 수 있습니다. 예를 들어 사전에 일부 주문을 추가하고 있으며 총 주문량을 계속 업데이트하려고한다고 가정합니다. 다음과 같은 코드를 작성할 수 있습니다.
private ConcurrentDictionary<int, Order> _dict;
private Decimal _totalAmount = 0;
while (await enumerator.MoveNextAsync())
{
Order order = enumerator.Current;
_dict.TryAdd(order.Id, order);
_totalAmount += order.Amount;
}
이 코드는 스레드 안전하지 않습니다. _totalAmount
필드를 업데이트하는 여러 스레드가 필드를 손상된 상태로 둘 수 있습니다. 따라서 다음과 같이 보호하려고 할 수 있습니다 lock
.
_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;
이 코드는 “safer”이지만 여전히 스레드 안전하지 않습니다. _totalAmount
사전의 항목과 일치 한다는 보장은 없습니다 . 다른 스레드는 UI 요소를 업데이트하기 위해이 값을 읽으려고 시도 할 수 있습니다.
Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);
은 totalAmount
사전에 주문의 수에 일치하지 않을 수 있습니다. 표시된 통계가 잘못되었을 수 있습니다. 이 시점 lock
에서 사전 업데이트를 포함하도록 보호를 확장해야합니다 .
// Thread A
lock (_locker)
{
_dict.TryAdd(order.Id, order);
_totalAmount += order.Amount;
}
// Thread B
int ordersCount;
Decimal totalAmount;
lock (_locker)
{
ordersCount = _dict.Count;
totalAmount = _totalAmount;
}
UpdateUI(ordersCount, totalAmount);
이 코드는 완벽하게 안전하지만 a를 사용하면 얻을 수있는 모든 이점 ConcurrentDictionary
이 사라졌습니다.
Dictionary
내부의 내부 잠금ConcurrentDictionary
이 이제 낭비되고 중복 되기 때문에 성능을 normal보다 사용하는 것보다 나빠졌습니다 .- 공유 변수의 보호되지 않은 사용을 위해 모든 코드를 검토해야합니다.
- 어색한 API (
TryAdd
?,AddOrUpdate
?)를 사용하고 있습니다.
따라서 내 조언은 : Dictionary
+로 시작 하고이 옵션이 실제로 실행 가능한 경우 lock
나중에 ConcurrentDictionary
성능 최적화로 업그레이드하는 옵션을 유지하십시오 . 많은 경우에 그렇지 않습니다.