.NET-사전 잠금과 동시 사전
ConcurrentDictionary
유형 에 대한 충분한 정보를 찾을 수 없으므로 여기에 대해 물어볼 것이라고 생각했습니다.
현재 a Dictionary
를 사용하여 여러 스레드 (스레드 풀에서 정확한 스레드 수 없음)로 지속적으로 액세스하는 모든 사용자를 보유하고 있으며 액세스를 동기화했습니다.
최근에 .NET 4.0에 스레드 안전 컬렉션 세트가 있음을 발견했으며 매우 기쁘게 생각합니다. Dictionary
동기화 된 액세스 가있는 일반 옵션을 사용 하거나 ConcurrentDictionary
이미 스레드로부터 안전 하는 옵션이 있기 때문에 '보다 효율적이고 관리하기 쉬운'옵션이 무엇인지 궁금 합니다.
.NET 4.0에 대한 참조 ConcurrentDictionary
스레드 세이프 콜렉션과 비스 레드 세이프 콜렉션은 다른 방식으로 볼 수 있습니다.
계산대를 제외하고 점원이없는 상점을 고려하십시오. 사람들이 책임감있게 행동하지 않으면 많은 문제가 있습니다. 예를 들어, 고객이 피라미드 캔에서 캔을 가져 가고 있지만 점원이 현재 피라미드를 짓고 있다면 모든 것이 망가질 것입니다. 또는 두 명의 고객이 동시에 같은 상품을 구매하면 누가 이기는가? 싸움이 있을까요? 이것은 스레드로부터 안전하지 않은 콜렉션입니다. 문제를 피할 수있는 방법은 많이 있지만, 모두 일종의 잠금 또는 어떤 방식 으로든 명시적인 액세스가 필요합니다.
반면에 책상에 점원이있는 상점을 생각해보십시오. 상점을 통해서만 쇼핑 할 수 있습니다. 당신은 줄을 서서 그에게 물건을 요구하고, 그는 그것을 당신에게 가져오고, 당신은 줄을 벗어납니다. 여러 개의 물품이 필요한 경우, 각 왕복에서 기억할 수있는만큼만 물품을 수령 할 수 있지만, 점원의 호그를 피하지 않도록주의해야합니다. 이렇게하면 다른 고객이 뒤에서 줄을 서게됩니다.
이제 이것을 고려하십시오. 점원 한 명이있는 상점에서 줄 앞에 줄을 서서 점원에게 "화장지가 있습니까?"라고 물으면 "예"라고 말한 다음 "좋아요." "내가 얼마나 필요한지 알면 다시 연락 드리겠습니다."그런 다음 줄 앞에서 돌아올 때마다 매장을 매진 할 수 있습니다. 이 시나리오는 스레드 세이프 콜렉션에 의해 방지되지 않습니다.
스레드 세이프 컬렉션은 내부 데이터 구조가 여러 스레드에서 액세스 된 경우에도 항상 유효한지 확인합니다.
스레드로부터 안전하지 않은 콜렉션에는 그러한 보장이 제공되지 않습니다. 예를 들어, 한 스레드의 이진 트리에 무언가를 추가하고 다른 스레드가 트리를 재조정하는 데 바쁘지만 항목이 추가되거나 그 이후에도 트리가 여전히 유효하다는 보장은 없습니다. 희망을 넘어서 손상 될 수 있습니다.
그러나 스레드 세이프 컬렉션은 스레드에 대한 순차 작업이 내부 데이터 구조의 동일한 "스냅 샷"에서 모두 작동한다고 보장하지 않습니다. 이는 다음과 같은 코드가있는 경우를 의미합니다.
if (tree.Count > 0)
Debug.WriteLine(tree.First().ToString());
당신은 inbetween 때문에 NullReferenceException이를 얻을 수 있습니다 tree.Count
및 tree.First()
다른 스레드 수단이 나무의 나머지 노드 없애 게했다, First()
반환됩니다 null
.
이 시나리오의 경우 해당 컬렉션에 원하는 것을 얻을 수있는 안전한 방법이 있는지 확인해야합니다. 위의 코드를 다시 작성해야하거나 잠 가야 할 수도 있습니다.
스레드 세이프를 사용하는 경우에도 여전히 세심한주의가 필요합니다. 스레드 세이프가 모든 스레딩 문제를 무시할 수있는 것은 아닙니다. 컬렉션이 스레드로부터 안전하다고 광고하면 일반적으로 여러 스레드가 동시에 읽고 쓰는 경우에도 일관성있는 상태로 유지됩니다. 그러나 이것이 단일 스레드가 여러 메소드를 호출하는 경우 "논리적"결과 시퀀스를 보게된다는 의미는 아닙니다.
예를 들어, 먼저 키가 있는지 확인한 다음 나중에 해당 키에 해당하는 값을 얻는 경우 다른 스레드가 키를 제거했을 수 있기 때문에 해당 키가 더 이상 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와 함께 가고 싶습니다. 스레드로부터 안전한 프로그램을 만들려면 코드를 적게 작성해야합니다.
우리는 비슷한 1 시간마다 다시이 채워진 다음 여러 클라이언트 스레드에 의해 읽기 캐시 수집 ConcurrentDictionary, 사용했던 용액에을 (가)에 대한 예 스레드 안전인가? 질문.
이를 ReadOnlyDictionary로 변경하면 전반적인 성능이 향상되었습니다.
는 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;
}
This code is not thread safe. Multiple threads updating the _totalAmount
field may leave it in a corrupted state. So you may try to protect it with a lock
:
_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;
This code is "safer", but still not thread safe. There is no guarantee that the _totalAmount
is consistent with the entries in the dictionary. Another thread may try to read these values, to update a UI element:
Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);
The totalAmount
may not correspond to the count of orders in the dictionary. The displayed statistics could be wrong. At this point you will realize that you must extend the lock
protection to include the updating of the dictionary:
// 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);
This code is perfectly safe, but all the benefits of using a ConcurrentDictionary
are gone.
- The performance has become worse than using a normal
Dictionary
, because the internal locking inside theConcurrentDictionary
is now wasteful and redundant. - You must review all your code for unprotected uses of the shared variables.
- You are stuck with using an awkward API (
TryAdd
?,AddOrUpdate
?).
So my advice is: start with a Dictionary
+lock
, and keep the option of upgrading later to a ConcurrentDictionary
as a performance optimization, if this option is actually viable. In many cases it will not be.
참고URL : https://stackoverflow.com/questions/1949131/net-dictionary-locking-vs-concurrentdictionary
'Programing' 카테고리의 다른 글
heredoc을 파이핑하기위한 여러 줄 구문; (0) | 2020.07.16 |
---|---|
발을 정렬하는 방법? (0) | 2020.07.16 |
DomainName을 IP로 변환하는 Linux 명령 (0) | 2020.07.15 |
jQuery를 사용하여 요소의 모든 속성 가져 오기 (0) | 2020.07.15 |
JSONP를 제공하는 최고의 콘텐츠 유형? (0) | 2020.07.15 |