>  기사  >  백엔드 개발  >  .NET 다중 스레드 프로그래밍 - 동시 컬렉션

.NET 다중 스레드 프로그래밍 - 동시 컬렉션

黄舟
黄舟원래의
2017-02-06 14:29:511223검색

동시 컬렉션

1 동시 컬렉션을 사용하는 이유는 무엇입니까?

주요 이유는 다음과 같습니다.

  • System.Collections 및 System.Collections.Generic 네임스페이스에서 제공되는 클래식 목록, 컬렉션 및 배열은 스레드로부터 안전하지 않습니다. 동기화 메커니즘이 없으면 요소를 추가하고 제거하기 위한 동시 명령을 받아들이는 데 적합하지 않습니다.

  • 위의 클래식 컬렉션을 동시 코드로 사용하려면 복잡한 동기화 관리가 필요하므로 사용이 매우 불편합니다.

  • 복잡한 동기화 메커니즘을 사용하면 성능이 크게 저하됩니다.

  • NET Framework 4에서 제공되는 새로운 컬렉션은 잠금 사용 필요성을 최소화합니다. 이러한 새 컬렉션에서는 CAS(비교 및 교환) 명령과 메모리 장벽을 사용하여 상호 배타적인 중량 잠금 사용을 방지합니다. 이는 성능을 보장합니다.

참고: 클래식 컬렉션에 비해 동시 컬렉션은 더 큰 오버헤드를 가지므로 직렬 코드에서 동시 컬렉션을 사용하는 것은 의미가 없으며 추가 오버헤드만 증가하고 클래식에 액세스하는 것보다 속도가 느립니다. 수집.

2개의 동시 컬렉션

1) ConcurrentQueue: 스레드로부터 안전한 FIFO(선입선출) 컬렉션

주요 메서드:

  • Enqueue(T 항목); 컬렉션의 마지막에 객체를 추가합니다.

  • TryDequeue(out T result); 컬렉션 시작 부분에서 개체를 제거하고 반환하려고 시도합니다. 반환 값은 작업이 성공했는지 여부를 나타냅니다.

  • TryPeek(out T result); 반환 값은 작업이 성공했는지 여부를 나타냅니다.

설명:

  • ConcurrentQueue는 완전히 잠금이 없지만 CAS 작업이 실패하고 리소스 경합에 직면할 경우 ConcurrentQueue를 회전하여 재시도할 수 있습니다. 작업.

  • ConcurrentQueue는 FIFO 컬렉션이므로 진입 및 퇴장 순서와 관련이 없는 경우에는 ConcurrentQueue를 사용하지 마세요.

2) ConcurrentStack: 스레드로부터 안전한 LIFO(후입선출) 컬렉션

주요 메서드 및 속성:

  • 푸시(T 항목); 컬렉션 상단에 개체를 삽입합니다.

  • TryPop(out T result); 컬렉션의 맨 위에 있는 개체를 팝하고 반환하려고 시도합니다. 반환 값은 작업이 성공했는지 여부를 나타냅니다.

  • TryPeek(out T result); 반환 값은 작업이 성공했는지 여부를 나타냅니다.

  • IsEmpty { get; } 컬렉션이 비어 있는지 여부를 나타냅니다.

  • PushRange(T[] items); 컬렉션의 맨 위에 여러 개체를 삽입합니다.

  • TryPopRange(T[] items); 상단에 여러 요소를 팝하고 반환 결과는 팝된 요소의 수입니다.

참고:

  • ConcurrentQueue와 유사하게 ConcurrentStack은 잠금이 전혀 없지만 CAS 작업이 실패하고 리소스 경합에 직면할 경우 회전하고 작업을 다시 시도합니다.

  • 컬렉션에 요소가 포함되어 있는지 확인하려면 Count 속성이 0보다 큰지 판단하는 대신 IsEmpty 속성을 사용하세요. Count 호출은 IsEmpty 호출보다 비용이 더 많이 듭니다.

  • PushRange(T[] 항목) 및 TryPopRange(T[] 항목)를 사용할 때 버퍼링으로 인한 추가 오버헤드와 추가 메모리 소비에 유의하세요.

3) ConcurrentBag: 반복 가능한 요소가 있는 순서가 지정되지 않은 컬렉션

주요 메소드 및 속성:

  • TryPeek(out T result) ); 개체를 제거하지 않고 컬렉션에서 개체를 반환하려고 시도합니다. 반환 값은 개체를 성공적으로 얻었는지 여부를 나타냅니다.

  • TryTake(out T result); 컬렉션에서 개체를 반환하고 개체를 제거하려고 시도합니다. 반환 값은 개체를 성공적으로 얻었는지 여부를 나타냅니다.

  • Add(T 항목)을 컬렉션에 추가합니다.

  • IsEmpty { get; } 설명은 ConcurrentStack과 동일합니다

설명:

  • 각각에 대한 ConcurrentBag 컬렉션에 액세스하는 스레드는 로컬 큐를 유지 관리하고 가능한 경우 잠금 없는 방식으로 로컬 큐에 액세스합니다.

  • ConcurrentBag는 동일한 스레드에서 요소를 추가하고 제거할 때 매우 효율적입니다.

  • ConcurrentBag는 때때로 잠금이 필요하기 때문에 생산자 스레드와 소비자 스레드가 완전히 분리되는 시나리오에서는 매우 비효율적입니다.

  • ConcurrentBag의 IsEmpty 호출은 순서가 지정되지 않은 그룹의 모든 잠금을 일시적으로 획득해야 하기 때문에 매우 비쌉니다.

4) BlockingCollection:

System.Collections.Concurrent.IProducerConsumerCollection을 구현하는 스레드로부터 안전한 컬렉션으로 차단 및 제한 기능을 제공합니다

주로 메서드 및 속성:

  • BlockingCollection(intboundedCapacity); 컬렉션 제한 크기를 나타냅니다.

  • CompleteAdding(); BlockingCollection 인스턴스를 더 이상 추가를 허용하지 않는 것으로 표시합니다.

  • IsCompleted { get; } 이 컬렉션이 완료되고 비어 있는 것으로 표시되었는지 여부입니다.

  • GetConsumingEnumerable(); 컬렉션에서 제거된 요소를 제거하고 반환합니다.

  • Add(T 항목);

  • TryTake(T 항목, int millisecondsTimeout, CancellationToken cancelToken);

지침:

  • BlockingCollection 사용( ) 생성자는 BlockingCollection을 인스턴스화합니다. 즉,boundedCapacity가 설정되지 않았음을 의미하며,boundedCapacity는 기본값인 int.MaxValue입니다.

  • Bounded: BlockingCollection(intboundedCapacity)를 사용하여boundedCapacity 값을 설정합니다. 컬렉션 용량이 이 값에 도달하면 요소가 삭제될 때까지 BlockingCollection에 요소를 추가하는 스레드가 차단됩니다. .

경계 기능은 메모리 내 컬렉션의 최대 크기를 제어하는데, 이는 많은 수의 요소를 처리해야 할 때 매우 유용합니다.

  • 기본적으로 BlockingCollection은 ConcurrentQueue를 캡슐화합니다. ConcurrentStack 및 ConcurrentBag를 포함하여 생성자에서 IProducerConsumerCollection 인터페이스를 구현하는 동시 컬렉션을 지정할 수 있습니다.

  • 이 컬렉션을 사용하면 무기한 기다릴 위험이 있으므로 TryTake를 사용하는 것이 더 편리합니다. TryTake는 시간 초과 제어 기능을 제공하고 지정된 시간 내에 컬렉션에서 항목을 제거할 수 있기 때문입니다. .은 참이고, 그렇지 않으면 거짓입니다.

5) ConcurrentDictionary: 여러 스레드에서 동시에 액세스할 수 있는 스레드로부터 안전한 키-값 쌍 컬렉션입니다.

기본 메서드

AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory) 지정된 키가 아직 존재하지 않는 경우 사전에 키/값 쌍을 추가합니다. 중간; 지정된 키가 이미 존재하는 경우 사전의 키/값 쌍을 업데이트합니다.

  • GetOrAdd(TKey key, TValue value); 지정된 키가 아직 존재하지 않는 경우 사전에 키/값 쌍을 추가합니다.

  • TryRemove(TKey key, out TValue value); 사전에서 제거하고 지정된 키가 있는 값을 반환해 봅니다.

  • TryUpdate(TKey key, TValue newValue, TValue CompareValue); 지정된 키의 기존 값을 지정된 값과 비교하여 같으면 세 번째 값으로 키를 업데이트합니다.

참고:

  • ConcurrentDictionary는 읽기 작업 시 잠금이 전혀 없습니다. ConcurrentDictionary는 여러 작업이나 스레드가 요소를 추가하거나 데이터를 수정할 때 세분화된 잠금을 사용합니다. 세분화된 잠금을 사용하면 전체 사전이 아니라 실제로 잠궈야 하는 부분만 잠깁니다.

6) IProducerConsumerCollection: 생산자/소비자가 스레드로부터 안전한 컬렉션을 작동하는 방법을 정의합니다. 이 인터페이스는 System.Collections.Concurrent.BlockingCollection과 같은 상위 수준 추상화가 기본 저장소 메커니즘으로 컬렉션을 사용할 수 있도록 (생산자/소비자 컬렉션에 대한) 통합 표현을 제공합니다.

3. 공통 패턴

1) 병렬 생산자-소비자 패턴

정의:

생산자와 소비자가 이 패턴에 있습니다. 두 가지 유형의 객체 모델 , 소비자는 생산자의 결과에 의존하고, 생산자는 결과를 생성하고, 소비자는 결과를 사용합니다.

.NET 다중 스레드 프로그래밍 - 동시 컬렉션

그림 1 병렬 생산자-소비자 모델

설명:

  • 여기서 동시 수집이 사용되는 패턴은 동시 컬렉션은 이 패턴의 개체에 대한 병렬 작업을 지원하므로 적합합니다.

  • 동시 컬렉션을 사용하지 않는 경우 동기화 메커니즘을 추가해야 합니다. 이로 인해 프로그램이 더 복잡해지고 유지 관리 및 이해가 어려워지고 성능이 크게 저하됩니다.

  • 위 그림은 생산자-소비자 모델의 개략도입니다. 세로축은 생산자와 소비자가 동일한 타임라인에 있지 않지만 겹치는 부분입니다. 생성자가 먼저 결과를 생성한 다음 생성자가 생성한 데이터를 소비자가 실제로 사용한다는 것을 보여주기 위한 것입니다.

2) 파이프라인 패턴

정의:

파이프라인은 여러 단계로 구성되며, 각 단계는 일련의 생산자와 소비자로 구성됩니다. 일반적으로 이전 단계는 나중 단계의 생성자이며, 인접한 두 단계 사이의 버퍼 큐에 의존하여 각 단계를 동시에 실행할 수 있습니다.

.NET 다중 스레드 프로그래밍 - 동시 컬렉션

그림 2 병렬 파이프라인 모드

설명:

  • BlockingCollection 탱크 구역 대기열.

  • 파이프라인의 속도는 파이프라인의 가장 느린 단계의 속도와 거의 같습니다.

  • 위 그림은 파이프라인 모드의 개략도입니다. 여기에서는 가장 간단하고 기본적인 파이프라인 모드를 볼 수 있습니다. 각 단계에는 더 많은 데이터 처리가 포함됩니다.

4 사용법

ConcurrentBag 및 BlockingCollection만 예로 들었고, 다른 동시 컬렉션도 유사합니다.

ConcurrentBag

List<string> list = ......
ConcurrentBag<string> bags = new ConcurrentBag<string>();
Parallel.ForEach(list, (item) => 
{
    //对list中的每个元素进行处理然后,加入bags中
    bags.Add(itemAfter);
});

BlockingCollection - 생산자 소비자 패턴

public static void Execute()
{
            //调用Invoke,使得生产者任务和消费者任务并行执行
            //Producer方法和Customer方法在Invoke中的参数顺序任意,不论何种顺序都会获得正确的结果
            Parallel.Invoke(()=>Customer(),()=>Producer());
            Console.WriteLine(string.Join(",",customerColl));
}
//生产者集合
private static BlockingCollection<int> producerColl = new BlockingCollection<int>();
 //消费者集合
private static BlockingCollection<string> customerColl = new BlockingCollection<string>();
public static void Producer()
{
            //循环将数据加入生成者集合
            for (int i = 0; i < 100; i++)
            {
                producerColl.Add(i);
            }
            //设置信号,表明不在向生产者集合中加入新数据
            //可以设置更加复杂的通知形式,比如数据量达到一定值且其中的数据满足某一条件时就设置完成添加
            producerColl.CompleteAdding();
}
public static void Customer()
{
            //调用IsCompleted方法,判断生产者集合是否在添加数据,是否还有未"消费"的数据
            //注意不要使用IsAddingCompleted,IsAddingCompleted只表明集合标记为已完成添加,而不能说明其为空
            //而IsCompleted为ture时,那么IsAddingCompleted为ture且集合为空
            while (!producerColl.IsCompleted)
            {
                //调用Take或TryTake "消费"数据,消费一个,移除一个
                //TryAdd的好处是提供超时机制
                customerColl.Add(string.Format("消费:{0}", producerColl.Take()));
            }
}


위 내용은 .NET 멀티스레드 프로그래밍 - 동시 수집 내용입니다. 더 많은 관련 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.