Поделиться через


Практическое руководство. Написание цикла 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 (Long в Visual Basic). Параметр типа сообщает компилятору тип временной переменной, которая будет использоваться для хранения локального состояния потока. Выражение () => 0 (Function() 0 в Visual Basic) в этом примере означает, что локальная переменная потока инициализируется нулевым значением. Если параметр типа является ссылочным типом или типом значения, определяемым пользователем, Func будет выглядеть следующим образом.

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

Четвертым параметром типа является определение логики цикла. IntelliSense показывает, что он имеет тип Func<int, ParallelLoopState, long, long> или Func(Of Integer, ParallelLoopState, Long, Long). Лямбда-выражение ожидает, что три входных параметра будут иметь порядок, соответствующий этим типам. Последний параметр типа — это возвращаемый тип. В этом случае типом является long, поскольку это тип, заданный в параметре типа For. Пользователь вызывает эту переменную subtotal в лямбда-выражении и возвращает ее. Возвращаемое значение используется для инициализации промежуточного итога в каждой последующей итерации. Можно также представить последний параметр просто как значение, передаваемое каждой итерации, а затем делегату localFinally по завершении последней итерации.

Пятым параметром является определение метода, вызываемого один раз по завершении всех итераций в данном потоке. Тип входного параметра снова соответствует параметру типа метода For и типу, возвращаемому лямбда-выражением тела. В этом примере значение добавляется к переменной в области класса потокобезопасным способом. С помощью локальной переменной потока можно избежать записи в эту переменную класса в каждой итерации каждого потока.

Дополнительные сведения об использовании лямбда-выражений см. в разделе Лямбда-выражения в PLINQ и библиотеке параллельных задач.

См. также

Основные понятия

Параллелизм данных (библиотека параллельных задач)

Параллельное программирование в .NET Framework

Библиотека параллельных задач

Лямбда-выражения в PLINQ и библиотеке параллельных задач