방법: 스레드 로컬 변수를 사용하는 Parallel.For 루프 작성
이 예제에서는 스레드 로컬 변수를 사용하여 For 루프에 의해 만들어진 각 개별 작업의 상태를 저장하고 검색하는 방법을 보여 줍니다. 스레드 로컬 데이터를 사용하면 공유 상태에 대한 수많은 액세스를 동기화할 때 발생하는 오버헤드를 방지할 수 있습니다. 각 반복에서 공유 리소스에 쓰는 것이 아니라 작업에 대한 모든 반복이 완료될 때까지 값을 계산하고 저장합니다. 그런 다음 최종 결과를 공유 리소스에 한 번만 쓰거나 다른 메서드에 전달할 수 있습니다.
예제
'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(x) Interlocked.Add(total, x))
Console.WriteLine("The total is {0}", total)
Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub
End Module
namespace ThreadLocalFor
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Test
{
static void Main()
{
int[] nums = Enumerable.Range(0, 1000000).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;
},
(x) => Interlocked.Add(ref total, x)
);
Console.WriteLine("The total is {0}", total);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
}
모든 For 메서드의 처음 두 매개 변수는 시작 및 종료 반복 값을 지정합니다. 이 메서드 오버로드의 세 번째 매개 변수에서는 로컬 상태를 초기화합니다. 이때 로컬 상태"란 현재 스레드에서 루프의 첫 번째 반복 직전부터 마지막 반복 직후까지의 수명 기간을 갖는 변수를 의미합니다.
세 번째 매개 변수의 형식은 Func<TResult>입니다. 여기서 TResult는 스레드 로컬 상태를 저장하는 변수의 형식입니다. 이 예제에서는 제네릭 버전의 메서드가 사용되며 형식 매개 변수는 long(Visual Basic에서는 Long)입니다. 이 형식 매개 변수는 스레드 로컬 상태를 저장하는 데 사용될 임시 변수의 형식을 컴파일러에 알려 줍니다. 이 예제에서 () => 0(Function() 0) 식은 스레드 지역 변수가 0으로 초기화됨을 나타냅니다. 이 형식 매개 변수가 참조 형식이거나 사용자 정의 값 형식인 경우 이 Func는 다음과 같이 나타납니다.
() => new MyClass()
Function() new MyClass()
네 번째 형식 매개 변수에서는 루프 논리를 정의합니다. IntelliSense에서는 해당 형식이 Func<int, ParallelLoopState, long, long> 또는 Func(Of Integer, ParallelLoopState, Long, Long)임을 보여 줍니다. 람다 식에서는 세 개의 입력 매개 변수를 이러한 형식에 해당하는 순서와 동일한 순서로 사용해야 합니다. 마지막 형식 매개 변수는 반환 값입니다. 이 예제의 경우에는 For 형식 매개 변수에 지정한 형식이 long이므로 long 형식이 사용됩니다. 람다 식에서 해당 변수 subtotal을 호출하고 반환합니다. 반환 값은 각 후속 반복에서 부분합을 초기화하는 데 사용됩니다. 이 마지막 매개 변수는 각 반복에 전달되다가 마지막 반복이 완료될 때는 localFinally 대리자에 전달되는 값이라고 생각할 수도 있습니다.
5번째 매개 변수에서는 이 스레드의 모든 반복이 완료된 후 한 번만 호출되는 메서드를 정의합니다. 이 입력 매개 변수의 형식은 다시 For 메서드의 형식 매개 변수와 본문 람다 식에서 반환되는 형식에 해당합니다. 이 예제에서는 스레드로부터 안전한 방식으로 클래스 범위의 변수에 값이 추가됩니다. 지금까지 모든 스레드의 모든 반복에서 이 클래스 변수에 값을 쓸 필요가 없도록 스레드 로컬 변수를 사용하는 방법을 살펴보았습니다.
람다 식을 사용하는 방법에 대한 자세한 내용은 PLINQ 및 TPL의 람다 식을 참조하십시오.