Parallel.ForEach는 활성 스레드 수를 제한합니까?


107

이 코드가 주어지면 :

var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
    DoSomething(someString);
});

1000 개의 모든 스레드가 거의 동시에 생성됩니까?

답변:


149

아니요, 1000 개의 스레드를 시작하지 않습니다. 예, 사용되는 스레드 수를 제한합니다. Parallel Extensions는 물리적으로 보유한 코어 수 이미 사용중인 코어 수에 따라 적절한 수의 코어를 사용합니다 . 각 코어에 작업을 할당 한 다음 작업 도용 이라는 기술을 사용하여 각 스레드가 자신의 대기열을 효율적으로 처리하고 실제로 필요할 때만 값 비싼 크로스 스레드 액세스를 수행하면됩니다.

상기 봐 가지고 PFX 팀 블로그 에 대한 부하 가 작업을 할당하는 방법에 대한 정보 및 기타 주제의 모든 종류.

어떤 경우에는 원하는 병렬 처리 수준도 지정할 수 있습니다.


2
저는 Parallel.ForEach (FilePathArray, path => ...)를 사용하여 오늘 밤 약 24,000 개의 파일을 읽고 제가 읽은 각 파일에 대해 하나의 새 파일을 생성했습니다. 매우 간단한 코드입니다. 6 개의 스레드만으로도 7200RPM 디스크를 압도 할 수있는 것 같습니다. 저는 100 % 사용률에서 읽었습니다. 몇 시간 동안 Parallel 라이브러리가 8,000 개 이상의 스레드를 스핀 오프하는 것을 지켜 봤습니다. MaxDegreeOfParallelism을 사용하여 테스트했고 8000 개 이상의 스레드가 사라진 것을 확인했습니다. 지금은 동일한 방식으로 여러 번 테스트했습니다. 결과.
제이크 드류에게

그것은 수있는 일부 타락한 '해봐요'1000 개 스레드를 시작합니다. (현재 프로덕션 코드에서 제한을 설정하지 못하고 200 개 이상의 스레드를 생성하여 SQL 연결 풀을 터뜨리는 문제를 처리하고있는 경우와 같이 사소하게 추론 할 수없는 작업에 대해 Max DOP를 설정하는 것이 좋습니다. 명시 적으로 CPU 바운드에 대한 것입니다.)
user2864740


28

단일 코어 머신에서 ... Parallel.ForEach 컬렉션의 여러 파티션 (청크)이 여러 스레드 사이에서 작업하고 있지만이 수는 알고리즘을 기반으로 계산되며이 수는 작업을 지속적으로 모니터링하는 것으로 보입니다. ForEach에 할당하는 스레드입니다. 따라서 ForEach의 본문 부분이 오래 실행되는 IO 바인딩 / 차단 기능을 호출하여 스레드를 대기 상태로두면 알고리즘은 더 많은 스레드를 생성하고 그 사이에 컬렉션을 다시 분할합니다 . 스레드가 빠르게 완료되고 예를 들어 단순히 일부 숫자를 계산하는 것과 같이 IO 스레드를 차단하지 않으면알고리즘은 처리량 (각 반복의 평균 완료 시간)에 대해 알고리즘이 최적이라고 간주하는 지점까지 스레드 수를 늘리거나 줄 입니다.

기본적으로 모든 다양한 병렬 라이브러리 함수 뒤에있는 스레드 풀은 사용할 최적의 스레드 수를 계산합니다. 물리적 프로세서 코어의 수는 방정식의 일부일뿐입니다. 코어 수와 생성 된 스레드 수 사이에는 단순한 일대일 관계가 없습니다.

동기화 스레드의 취소 및 처리에 대한 문서는 매우 유용하지 않습니다. MS가 MSDN에서 더 나은 예제를 제공 할 수 있기를 바랍니다.

잊지 마세요. 본문 코드는 모든 일반적인 스레드 안전 고려 사항과 함께 여러 스레드에서 실행되도록 작성되어야합니다. 프레임 워크는 아직 해당 요소를 추상화하지 않습니다.


1
"..ForEach의 본문 부분이 스레드를 대기 상태로 유지하는 장기 실행 차단 함수를 호출하면 알고리즘은 더 많은 스레드를 생성합니다."- 퇴화 된 경우 이는 허용 된만큼의 스레드가 생성 될 수 있음을 의미합니다. ThreadPool 당.
user2864740

2
당신이 맞습니다. IO의 경우 내가 직접 디버깅했을 때 +100 개의 스레드를 할당 할 수 있습니다.
FindOutIslamNow

5

프로세서 / 코어 수에 따라 최적의 스레드 수를 계산합니다. 한 번에 모두 스폰되지는 않습니다.



4

좋은 질문입니다. 귀하의 예에서 병렬화 수준은 쿼드 코어 프로세서에서도 매우 낮지 만 일부 대기 상태에서는 병렬화 수준이 상당히 높아질 수 있습니다.

// Max concurrency: 5
[Test]
public void Memory_Operations()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    Parallel.ForEach<string>(arrayStrings, someString =>
    {
        monitor.Add(monitor.Count);
        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

이제 HTTP 요청을 시뮬레이션하기 위해 대기 작업이 추가되면 어떻게되는지 살펴보십시오.

// Max concurrency: 34
[Test]
public void Waiting_Operations()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    Parallel.ForEach<string>(arrayStrings, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(1000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

아직 변경하지 않았으며 동시성 / 병렬화 수준이 급격히 올라갔습니다. 동시성은 ParallelOptions.MaxDegreeOfParallelism.

// Max concurrency: 43
[Test]
public void Test()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(1000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

// Max concurrency: 391
[Test]
public void Test()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(100000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

설정을 권장합니다 ParallelOptions.MaxDegreeOfParallelism. 사용중인 스레드 수를 반드시 증가시키는 것은 아니지만 문제가되는 스레드 수만 시작하도록 보장합니다.

마지막으로 질문에 답하기 위해 모든 스레드가 한 번에 시작되는 것은 아닙니다. 경합 조건 테스트와 같이 완벽하게 병렬로 호출하려는 경우 Parallel.Invoke를 사용하십시오.

// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623368346
// 636462943623368346
// 636462943623373351
// 636462943623393364
// 636462943623393364
[Test]
public void Test()
{
    ConcurrentBag<string> monitor = new ConcurrentBag<string>();
    ConcurrentBag<string> monitorOut = new ConcurrentBag<string>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(DateTime.UtcNow.Ticks.ToString());
        monitor.TryTake(out string result);
        monitorOut.Add(result);
    });

    var startTimes = monitorOut.OrderBy(x => x.ToString()).ToList();
    Console.WriteLine(string.Join(Environment.NewLine, startTimes.Take(10)));
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.