Практическое руководство. Написание цикла 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 и библиотеке параллельных задач.
См. также
Основные понятия
Параллелизм данных (библиотека параллельных задач)