2016-11-29 7 views
2

피드백 열거 자로 구현 된 끝없는 열거 형을 반복하는 간단한 프로그램이 있습니다. 나는 TPL과 PLINQ에서 이것을 구현했다. 두 예 모두 예측 가능한 반복 횟수 (PLINQ의 경우 8 개, TPL의 경우 3 개)를 초과하면 멈 춥니 다. TPL/PLINQ를 사용하지 않고 코드가 실행되면 정상적으로 실행됩니다. 난 threadsafe 방식뿐만 아니라 비 threadsafe 방식으로 열거자를 구현했다. 전자의 경우 병렬 처리 수준이 1로 제한되는 경우 사용할 수 있습니다 (예제의 경우와 동일). non-threadsafe 열거 자 (unsumerafe enumerator)는 매우 간단하며 '멋진'.NET 라이브러리 클래스에 의존하지 않습니다. 병렬 처리 수준을 높이면 교착 상태가 증가하기 전에 수행되는 반복 횟수가 늘어납니다. 예를 들어 PLINQ의 경우 반복 횟수는 8 * 병렬 처리 수준입니다.
열거 (비 스레드)
반복 열거 자의 PLINQ 반복으로 인해 교착 상태가 발생합니다.

public class SimpleEnumerable<T>: IEnumerable<T> 
{ 
    private T _value; 
    private readonly AutoResetEvent _releaseValueEvent = new AutoResetEvent(false); 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     while(true) 
     { 
      _releaseValueEvent.WaitOne(); 
      yield return _value; 
     } 
    } 

    public void OnNext(T value) 
    { 
     _value = value; 
     _releaseValueEvent.Set(); 
    } 
} 

열거 (스레드)

public class SimpleEnumerable<T>: IEnumerable<T> 
{ 
    private readonly BlockingCollection<T> _blockingCollection = new BlockingCollection<T>(); 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     while(true) 
     { 
      yield return _blockingCollection.Take(); 
     } 
    } 

    public void OnNext(T value) 
    { 
     _blockingCollection.Add(value); 
    } 
} 

PLINQ 예 : 여기

는 반복자이다

,745,
public static void Main(string[] args) 
{ 
    var enumerable = new SimpleEnumerable<int>(); 
    enumerable.OnNext(0); 

    enumerable 
     .Do(i => Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}")) 
     .AsParallel() 
     .WithDegreeOfParallelism(1) 
     .ForEach 
     (
      i => 
      { 
       Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}"); 
       enumerable.OnNext(i+1); 
      } 
     ); 
} 

TPL 예 : 호출 스택의 내 분석에

public static void Main(string[] args) 
{ 
    var enumerable = new SimpleEnumerable<int>(); 
    enumerable.OnNext(0); 

    Parallel.ForEach 
    (
     enumerable, 
     new ParallelOptions { MaxDegreeOfParallelism = 1}, 
     i => 
     { 
      Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}"); 
      enumerable.OnNext(i+1); 
     } 
    ); 
} 

자료, PLINQ 및 TPL에서 모두 파티션 프로그램과 관련된 방법에서 발생하는 교착 상태가 나타납니다,하지만 난 아니다 이것을 어떻게 해석해야하는지.

시행 착오를 거쳐 PLINQ enumerablePartitioner.Create(enumerable, EnumerablePartitionerOptions.NoBuffering)에 수정했지만 교착 상태가 발생하는 이유를 모르겠습니다.

나는 버그의 근본 원인을 찾는 것에 매우 관심이 있습니다.

인위적인 예제입니다. 나는 코드의 비평을 찾고있는 것이 아니라 이 교착 상태가되는 이유를 찾고있다. 특히, PLINQ 예제에서 .AsParallel().WithDegreeOfParallelism(1) 행이 주석 처리 된 경우 코드는 올바르게 작동합니다.

+0

PLINQ와 Parallel은 교착 상태를 일으키지 않습니다. 현재 스레드를 N 명의 다른 사람들과 함께 사용하여 병렬로 데이터를 처리합니다. –

+0

@PanagiotisKanavos 분명히 교착 상태가 반복기에 있습니다. 언뜻보기에, 나는 놀랍지는 않습니다. 확실히 안전하지 않습니다. – Servy

+0

@Servy 가장 분명한 문제로 시작했습니다. 이터레이터는 아주 이상한 구조입니다. 간단한 10K int 배열은 병렬 실행을 테스트하기에 충분합니다. Interlocked.Increment는 카운트를 유지하는 아주 좋은 방법입니다. 이 반복자는 그냥 블록 –

답변

2

실제로 논리 값 시퀀스가 ​​없으므로 처음에 IEnumerable을 만들려고 만하는 것이 의미가 없습니다. 또한 복수 스레드에서 사용할 수있는 IEnumerator을 만들지 않으려 고합니다. 사실, IEnumerator이 노출하는 인터페이스가 실제로 원하는 것을 노출시키지 못하기 때문에 광기가 드러납니다. 여러 스레드에서 사용되는 기본 데이터 원본을 기반으로 반환 할 데이터를 계산하는 단일 스레드에서만 사용하는 IEnumerator을 만들 수 있습니다. 그 차이는 다소 다릅니다.

다른 스레드에서 실행되는 제작자와 소비자를 만들고 싶다면 BlockingCollection 주위에 자신의 "래퍼"를 만들지 말고 * 단지 BlockingCollection을 사용하십시오. 제작자가 그것에 추가하게하고, 소비자가 그것을 읽도록하십시오. 소비자는 항목을 반복하면서 항목을 반복하려는 경우 (일반적인 작업을 수행하려는 경우) GetConsumingEnumerable을 사용할 수 있습니다.

+0

내가 준 예제는 전혀 다른 코드에서 추출한 것입니다. 이 문제를 설명하기위한 인위적인 예입니다. 원래 코드는 열거 자 또는 블로킹 콜렉션을 사용하지 않았습니다. 나는 당신의 의견에 감사하지만, 나는 당신이 운동의 요점을 완전히 놓쳤다 고 말할 것입니다. 나는 코드가 무엇을하는지 또는 어떻게 코드가 그것을하고 있는지에 대한 비판을 많이 요구하지는 않았지만, PLINQ와 TPL에서 코드가 교착 상태에 빠졌습니다. –

+1

@TomasC 당신은 당신이 실제로 신경을 쓰는 코드가 전혀 보이지 않는다고 말하는 것은 당신이 보여준 코드가 당신이 관심을 갖는 것에 대해 어떤 정보도 얻을 수 없다는 것을 의미합니다. 열거자를 실제로 가지고 있지 않고 예제의 구현 세부 사항을 공유하지 않을 때 여러 스레드의 반복기를 사용하는 방법에 대해 질문하는 경우 상황에 대해 전혀 알지 못하며 분명히 주석을 달 수 없습니다 그 위에. 실제로 제공하는 코드에 대해서만 언급 할 수 있습니다. – Servy

+0

저는 열거자를 사용하는 방법을 잘 알고 있습니다. 여러 스레드의 열거자를 사용하는 방법에 대해서는 묻지 않았습니다. 나는 그것을하는 방법을 안다. 나는 단순히 교착 상태의 원인이되는 PLINQ 코드에서의 몇 가지 동작을 가지고 있으며, 왜 내가 교착 상태에 빠졌는지에 대한 이유를 얻기를 원했다. 내가 묻는 질문을 무시하는 것 같습니다. 즉 교착 상태가되는 이유는 무엇입니까? 그게 내가 알고 싶은 전부 야. 그렇지 않은 경우 귀하의 의견은 높이 평가되고 매우 통찰력이 있습니다. –