이 예제에서는 스레드 지역 변수를 사용하여 For 루프에서 만든 각 개별 태스크에서 상태를 저장하고 검색하는 방법을 보여 줍니다. 스레드 로컬 데이터를 사용하면 많은 수의 액세스를 공유 상태로 동기화하는 오버헤드를 방지할 수 있습니다. 각 반복에서 공유 리소스에 쓰는 대신 작업에 대한 모든 반복이 완료될 때까지 값을 계산하고 저장합니다. 그런 다음 최종 결과를 공유 리소스에 한 번 쓰거나 다른 메서드에 전달할 수 있습니다.
예시
다음 예제에서는 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 메서드를 호출하여 백만 개의 요소가 포함된 배열의 값 합계를 계산합니다. 각 요소의 값은 해당 인덱스입니다.
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 {total:N0}");
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 메서드의 처음 두 매개 변수는 시작 및 끝 반복 값을 지정합니다. 메서드의 이 오버로드에서 세 번째 매개 변수는 로컬 상태를 초기화하는 위치입니다. 이 컨텍스트에서 로컬 상태는 현재 스레드에서 루프의 첫 번째 반복 직전부터 마지막 반복 직후까지 수명이 확장되는 변수를 의미합니다.
세 번째 매개 변수의 형식은 TResult
스레드 로컬 상태를 저장할 변수의 형식인 Func<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()
네 번째 매개 변수는 루프 논리를 정의합니다. 서명이 C#에서는 Func<int, ParallelLoopState, long, long>
이거나 Visual Basic에서는 Func(Of Integer, ParallelLoopState, Long, Long)
인 대리자 또는 람다 식이어야 합니다. 첫 번째 매개 변수는 루프 반복에 대한 루프 카운터의 값입니다. 두 번째는 루프를 중단하는 데 사용할 수 있는 ParallelLoopState 개체입니다. 이 개체는 루프의 각 발생에 Parallel 클래스에 의해 제공됩니다. 세 번째 매개 변수는 스레드 지역 변수입니다. 마지막 매개 변수는 반환 형식입니다. 이 경우 형식은 For 형식 인수에 지정한 형식이므로 Int64. 해당 변수의 이름은 subtotal
람다 식에서 반환됩니다. 반환 값은 루프의 후속 반복마다 subtotal
초기화하는 데 사용됩니다. 또한 이 마지막 매개 변수는 각 반복에 전달된 다음 마지막 반복이 완료되면 localFinally
대리자에 전달되는 값으로 간주할 수 있습니다.
다섯 번째 매개 변수는 특정 스레드의 모든 반복이 완료된 후 한 번 호출되는 메서드를 정의합니다. 입력 인수의 형식은 다시 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 메서드의 형식 인수와 본문 람다 식에서 반환된 형식에 해당합니다. 이 예제에서 값은 Interlocked.Add 메서드를 호출하여 스레드로부터 안전한 방식으로 클래스 범위의 변수에 추가됩니다. 스레드 지역 변수를 사용하여 루프의 모든 반복에 대해 이 클래스 변수에 쓰지 않도록 했습니다.
람다 식을 사용하는 방법에 대한 자세한 내용은 PLINQ 및 TPL 람다 식을 참조하세요.
참고하십시오
.NET