Практическое руководство. Написание цикла Parallel.For с локальными переменными потокаHow to: Write a Parallel.For Loop with Thread-Local Variables

В этом примере показано, как использовать локальные переменные потока для хранения и получения состояния каждой отдельной задачи, создаваемой циклом For.This example shows how to use thread-local variables to store and retrieve state in each separate task that is created by a For loop. Благодаря локальным переменным потока вы можете избежать дополнительной нагрузки при синхронизации большого количества доступов к общему состоянию.By using thread-local data, you can avoid the overhead of synchronizing a large number of accesses to shared state. Вместо записи в общий ресурс при каждой итерации вы вычисляете и сохраняете значение до тех пор, пока не будут выполнены все итерации для задачи.Instead of writing to a shared resource on each iteration, you compute and store the value until all iterations for the task are complete. После этого вы можете однократно записать итоговый результат в общий ресурс или передать его в другой метод.You can then write the final result once to the shared resource, or pass it to another method.

ПримерExample

В следующем примере выполняется вызов метода For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) для вычисления суммы значений в массиве, содержащем один миллион элементов.The following example calls the For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) method to calculate the sum of the values in an array that contains one million elements. Значение каждого элемента равно его индексу.The value of each element is equal to its index.

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: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(x) Interlocked.Add(total, x))

        Console.WriteLine("The total is {0:N0}", total)
        Console.WriteLine("Press any key to exit")
        Console.ReadKey()
    End Sub

End Module

Два первых параметра каждого метода For задают начальное и конечное значения итерации.The first two parameters of every For method specify the beginning and ending iteration values. В этой перегрузке метода третий параметр используется для инициализации локального состояния.In this overload of the method, the third parameter is where you initialize your local state. В этом контексте локальное состояние означает переменную, время существования которой начинается непосредственно перед первой итерацией цикла в текущем потоке и заканчивается сразу же после последней итерации.In this context, local state means a variable whose lifetime extends from just before the first iteration of the loop on the current thread, to just after the last iteration.

Третий параметр имеет тип Func<TResult>, где TResult — это тип переменной для сохранения локального состояния потока.The type of the third parameter is a Func<TResult> where TResult is the type of the variable that will store the thread-local state. Этот тип определяется аргументом универсального типа, переданным во время вызова универсального метода For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>), в данном случае он равен Int64.Its type is defined by the generic type argument supplied when calling the generic For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) method, which in this case is Int64. Этот аргумент типа сообщает компилятору тип временной переменной, которая будет использоваться для хранения локального состояния в потоке.The type argument tells the compiler the type of the temporary variable that will be used to store the thread-local state. В этом примере выражение () => 0 (или Function() 0 в Visual Basic) инициализирует локальную переменную потока с нулевым значением.In this example, the expression () => 0 (or Function() 0 in Visual Basic) initializes the thread-local variable to zero. Если бы аргумент универсального типа имел ссылочный тип или тип пользовательского значения, выражение выглядело бы следующим образом:If the generic type argument is a reference type or user-defined value type, the expression would look like this:

() => new MyClass()  
Function() new MyClass()  

Четвертый параметр определяет логическую схему цикла.The fourth parameter defines the loop logic. Он должен быть делегатом или лямбда-выражением, сигнатура которого имеет значение Func<int, ParallelLoopState, long, long> в C# или Func(Of Integer, ParallelLoopState, Long, Long) в Visual Basic.It must be a delegate or lambda expression whose signature is Func<int, ParallelLoopState, long, long> in C# or Func(Of Integer, ParallelLoopState, Long, Long) in Visual Basic. Первый параметр представляет собой значение счетчика цикла для данной итерации цикла.The first parameter is the value of the loop counter for that iteration of the loop. Второй является объектом ParallelLoopState, который можно использовать для выхода из цикла; данный объект предоставляется классом Parallel каждому экземпляру цикла.The second is a ParallelLoopState object that can be used to break out of the loop; this object is provided by the Parallel class to each occurrence of the loop. Третий параметр представляет собой локальную переменную потока.The third parameter is the thread-local variable. Последний параметр является типом возвращаемого значения.The last parameter is the return type. В данном случае этот тип имеет значение Int64, так как именно его мы указали в аргументе типа For.In this case, the type is Int64 because that is the type we specified in the For type argument. Эта переменная называется subtotal и возвращается лямбда-выражением.That variable is named subtotal and is returned by the lambda expression. Возвращаемое значение используется для инициализации subtotal на каждой последующей итерации цикла.The return value is used to initialize subtotal on each subsequent iteration of the loop. Этот последний параметр также можно представить себе в виде значения, которое передается в каждую итерацию, а после выполнения последней итерации передается в делегат localFinally.You can also think of this last parameter as a value that is passed to each iteration, and then passed to the localFinally delegate when the last iteration is complete.

Пятый параметр задает метод, который вызывается один раз после выполнения всех итераций в определенном потоке.The fifth parameter defines the method that is called once, after all the iterations on a particular thread have completed. Тип входного аргумента опять соответствует аргументу типа метода For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) и типу, возвращаемому телом лямбда-выражения.The type of the input argument again corresponds to the type argument of the For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) method and the type returned by the body lambda expression. В данном примере это значение добавляется в переменную в области видимости класса потокобезопасным образом путем вызова метода Interlocked.Add.In this example, the value is added to a variable at class scope in a thread safe way by calling the Interlocked.Add method. Благодаря использованию локальной переменной потока нам не пришлось выполнять запись в эту переменную класса при каждой итерации цикла.By using a thread-local variable, we have avoided writing to this class variable on every iteration of the loop.

См. дополнительные сведения о лямбда-выражениях в PLINQ и TPL.For more information about how to use lambda expressions, see Lambda Expressions in PLINQ and TPL.

См. такжеSee also