방법: 스레드 로컬 변수를 사용하는 Parallel.For 루프 작성
이 예제에서는 스레드 지역 변수를 사용하여, For 루프에 의해 만들어진 각 별도 작업의 상태를 저장하고 검색하는 방법을 보여 줍니다. 스레드 로컬 데이터를 사용하여, 공유 상태에 대한 많은 수의 액세스를 동기화하는 오버헤드를 방지할 수 있습니다. 반복할 때마다 공유 리소스에 쓰는 대신 작업의 반복이 모두 완료될 때까지 값을 계산하여 저장합니다. 그런 다음 공유 리소스에 최종 결과를 한 번 쓰거나, 최종 결과를 다른 메서드로 전달할 수 있습니다.
예시
다음 예제에서는 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 메서드를 호출하여 1백만 개의 요소가 포함된 배열의 값 합계를 계산합니다. 각 요소의 값은 해당 인덱스와 같습니다.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Test
{
static void Main()
{
int[] nums = Enumerable.Range(0, 1_000_000).ToArray();
long total = 0;
// Use type parameter to make subtotal a long, not an int
Parallel.For<long>(0, nums.Length, () => 0,
(j, loop, subtotal) =>
{
subtotal += nums[j];
return subtotal;
},
subtotal => Interlocked.Add(ref total, subtotal));
Console.WriteLine("The total is {0:N0}", total);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
'How to: Write a Parallel.For Loop That Has Thread-Local Variables
Imports System.Threading
Imports System.Threading.Tasks
Module ForWithThreadLocal
Sub Main()
Dim nums As Integer() = Enumerable.Range(0, 1000000).ToArray()
Dim total As Long = 0
' Use type parameter to make subtotal a Long type. Function will overflow otherwise.
Parallel.For(Of Long)(0, nums.Length, Function() 0, Function(j, [loop], subtotal)
subtotal += nums(j)
Return subtotal
End Function, Function(subtotal) Interlocked.Add(total, subtotal))
Console.WriteLine("The total is {0:N0}", total)
Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub
End Module
모든 For 메서드의 처음 두 매개 변수는 시작 및 끝 반복 값을 지정합니다. 메서드의 이 오버로드에서 세 번째 매개 변수는 로컬 상태를 초기화하는 위치입니다. 이 컨텍스트에서 로컬 상태는 수명이 현재 스레드에 대한 루프의 첫 번째 반복 바로 전에서 마지막 반복 바로 후까지 연장되는 변수를 의미합니다.
세 번째 매개 변수의 형식은 Func<TResult>이며, 여기서 TResult
는 스레드 지역 변수를 저장하는 변수의 형식입니다. 해당 형식은 제네릭 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 메서드를 호출할 때 제공되는 제네릭 형식 인수에 의해 정의되며, 이 경우 Int64입니다. 형식 인수는 컴파일러에 스레드 지역 상태를 저장하는 데 사용되는 임시 변수의 형식을 알립니다. 이 예제에서 () => 0
(또는 Visual Basic의 경우 Function() 0
) 식은 스레드 지역 변수를 0으로 초기화합니다. 제네릭 형식 인수가 참조 형식 또는 사용자 정의 값 형식인 경우 식은 다음과 같습니다.
() => new MyClass()
Function() new MyClass()
네 번째 매개 변수는 루프 논리를 정의합니다. 이 매개 변수는 시그니처가 Func<int, ParallelLoopState, long, long>
(C#의 경우) 또는 Func(Of Integer, ParallelLoopState, Long, Long)
(Visual Basic의 경우)인 대리자 또는 람다 식이어야 합니다. 첫 번째 매개 변수는 루프의 해당 반복에 대한 루프 카운터의 값입니다. 두 번째 매개 변수는 루프를 중단하는 데 사용할 수 있는 ParallelLoopState 개체입니다. 이 개체는 Parallel 클래스에 의해 루프의 각 발생으로 제공됩니다. 세 번째 매개 변수는 스레드 지역 변수입니다. 마지막 매개 변수는 반환 형식입니다. 이 경우 반환 형식은 Int64입니다. 이 형식이 사용자가 For 형식 인수에 지정한 형식이기 때문입니다. 해당 변수의 이름은 subtotal
로 지정되며 람다 식에 의해 반환됩니다. 반환 값은 이후에 루프가 반복될 때마다 subtotal
을 초기화하는 데 사용됩니다. 또한 이 마지막 매개 변수를 각 반복에 전달된 다음 마지막 반복이 완료될 때 localFinally
대리자에게 전달되는 값으로 생각할 수 있습니다.
다섯 번째 매개 변수는 특정 스레드에 대한 모든 반복이 완료된 후 한 번 호출되는 메서드를 정의합니다. 입력 인수의 형식은 다시 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 메서드의 형식 인수 및 본문 람다 식에 의해 반환되는 형식에 해당합니다. 이 예제에서 값은 Interlocked.Add 메서드를 호출하여 스레드로부터 안전한 방법으로 클래스 범위에서 변수에 더해집니다. 스레드 지역 변수를 사용하여, 루프가 반복될 때마다 이 클래스 변수에 쓰는 것을 방지했습니다.
람다 식을 사용하는 방법은 PLINQ 및 TPL의 람다 식을 참조하세요.
참고 항목
.NET