Опубликован: 15.10.2009 | Доступ: свободный | Студентов: 896 / 248 | Оценка: 4.42 / 4.20 | Длительность: 08:22:00
Специальности: Программист
Лекция 2:

Конструкция Parallel.For

< Лекция 1 || Лекция 2: 12 || Лекция 3 >

Семинарское занятие № 2. Вариант Parallel.For с локальными состояниями

Конструкции Parallel.For/ForEach имеют несколько перегруженных (overloaded) вариантов, с помощью которых можно организовать передачу информации между итерациями цикла, исполняющимися в одном потоке. Вспомним, что при выполнении Parallel.For/ForEach каждый рабочий поток выполняет, в общем случае, несколько итераций. Например, при общем количестве итераций 100, на 4-хядерной машине, обычно будет запущено 4 потока, каждый из которых выполнит 25 итераций. Обычно, передача информации (например, подсчет какого-либо значения) между итерациями, исполняющимися в одном потоке, нужна для вычисления некоторого сводного значения по всем итерациям цикла.

Одним из вариантов перегруженной конструкции Parallel.For для поддержки вычислений такого рода выглядит так:

public static void For<TLocal> (
    int fromInclusive, int toExclusive, 
    Func<TLocal> threadLocalInit, 
    Action<int, ParallelState<TLocal> > body, 
    Action<TLocal> threadLocalFinally);

Здесь тип TLocal задает тип объекта, с которым будут работать итерации в рамках одного потока. Делегат threadLocalInit вызывается один раз для каждого потока, выделенного для исполнения группы итераций цикла, перед тем как этот поток начал исполнять свои итерации. Обычно с помощью этого делегата создается или инициализируется объект типа TLocal с которыми будут работать итерации.

Результат каждой итерации может быть записан в объект класса ParallelState<TLocal> который является одним из встроенных классов библиотеки PFX), передаваемый в качестве параметра в делегат body. Делегат body может обновлять специальное поле (а точнее, свойство) ThreadLocalState, которое имеет объект ParallelState<TLocal>, и которое имеет тип TLocal, и значение этого поля будет доступно следующим итерациям, исполняющимся в данном потоке. После того, как поток завершил исполнение своих итераций, в рамках его же, будет вызван делегат threadLocalFinally, которому в качестве параметра будет передано значение ThreadLocalState.

Примером использования одного из перегруженных вариантов конструкции Parallel.For может служить следующий код:

Parallel.For(0, N, () => new NonThreadSafeData(), (i,loop)= >
{
    UseData(loop.ThreadLocalState);
});

(Здесь не использован делегат threadLocalFinally из вышеприведенной сигнатуры).

В данном примере для каждого нового потока в рамках Parallel.For будет создан свой объект класса NonThreadSafeData (т.е., в данном случае TLocal = NonThreadSafeData ). Таким образом, объект NonThreadSafeData будет передаваться между итерациями одного потока, что позволит потоку безопасно использовать этот объект. При этом, таких объектов будет создано ровно столько, сколько потоков будет запущено. Отметим также, что локальная переменная loop в приведенном выше примере имеет тип ParallelState<TLocal> .

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

int total = 0;
Parallel.ForEach(data, ()=>0, (elem,i,loop)= >
{
    loop.ThreadLocalState += Process(elem);
},
partial => Interlocked.Add(ref total, partial));

Отметим, что в данном случае локальная переменная partial имеет тип TLocal, который, в данном примере, есть тип int (см. делегат threadLocalInit ).

Если требуется передавать между итерациями несколько общих переменных, то, как обычно делают в таких случаях, можно создать собственный класс из этих переменных, и уже его передавать как ThreadLocalState:

class MultipleValues { public int Total, Count; }

int total = 0, count = 0;
Parallel.ForEach(data, 
    ()=>new MultipleValues { Total=0, Count=0 }, 
    (elem,i,loop)= >
{
    loop.ThreadLocalState.Total += Process(elem);
    loop.ThreadLocalState.Count++;
},
partial => {
    Interlocked.Add(ref total, partial.Total);
    Interlocked.Add(ref count, partial.Count);
});

Задания:

Реализуйте подсчет суммы элементов массива с использованием предлагаемого подхода, кроме того, подсчитайте, сколько потоков было запущено при исполнении Parallel.For/ForEach в вашей программе.

Изучите понятие "анонимный класс" языка C# и выясните можно ли использовать анонимные классы вместо явного определения класса MultipleValues в приведенном выше примере,и если нельзя, то почему.

< Лекция 1 || Лекция 2: 12 || Лекция 3 >
Максим Полищук
Максим Полищук
"...Изучение и анализ примеров.
В и приведены описания и приложены исходные коды параллельных программ..."
Непонятно что такое - "В и приведены описания" и где именно приведены и приложены исходные коды.