5

У меня есть куча строк данных, и я хочу использовать Parallel.ForEach для вычисления некоторого значения на каждой строке, как это ...Несколько вызовов Parallel.ForEach, MemoryBarrier?

class DataRow 
{ 
    public double A { get; internal set; } 
    public double B { get; internal set; } 
    public double C { get; internal set; } 

    public DataRow() 
    { 
     A = double.NaN; 
     B = double.NaN; 
     C = double.NaN; 
    } 
} 

class Program 
{ 
    static void ParallelForEachToyExample() 
    { 
     var rnd = new Random(); 
     var df = new List<DataRow>(); 

     for (int i = 0; i < 10000000; i++) 
     { 
      var dr = new DataRow {A = rnd.NextDouble()}; 
      df.Add(dr); 
     } 

     // Ever Needed? (I) 
     //Thread.MemoryBarrier(); 

     // Parallel For Each (II) 
     Parallel.ForEach(df, dr => 
     { 
      dr.B = 2.0*dr.A; 
     }); 

     // Ever Needed? (III) 
     //Thread.MemoryBarrier(); 

     // Parallel For Each 2 (IV) 
     Parallel.ForEach(df, dr => 
     { 
      dr.C = 2.0 * dr.B; 
     }); 
    } 
} 

(В этом примере нет необходимости распараллеливания и если было, все могли бы войти в один Parallel.ForEach. Но это означает упрощенную версию некоторого кода, где имеет смысл настроить его таким образом).

Возможно ли, чтобы чтения были переупорядочены здесь, чтобы в итоге я получил строку данных, где B! = 2A или C! = 2B?

Скажем, первый Parallel.ForEach (II) назначает рабочий поток 42 для работы с строкой данных 0. И второй Parallel.ForEach (IV) назначает рабочий поток 43 для работы с строкой данных 0 (как только первая параллельная .ForEach заканчивается). Есть ли вероятность, что чтение dr.B для строки 0 в потоке 43 вернет double.NaN, поскольку он еще не видел запись из потока 42?

И если да, то вставляет ли барьер памяти при помощи III вообще? Это заставит обновления с первого Parallel.ForEach быть видимыми для всех потоков до того, как будет запущен второй Parallel.ForEach?

+0

Короче .. Я не думаю, что вам нужен явные барьеры памяти .. Образованные бы предположить, что реализация Parallel.ForEach имеет некоторую синхронизацию для завершения цикла/перед вызовом 'ForEach' возвращает –

+0

Учитывая лучшую картину вашего фактического кода, я мог бы дать вам лучший ответ, кроме« Нет, не беспокойтесь об этом. " :) – jdphenix

+0

Возможно, причина разделения немного ясна, если я скажу, что вычисление в каждой строке второго параллельного цикла (IV) зависит от некоторого значения, которое может быть известно только после завершения первого цикла (II). Скажем, нам нужна медиана значений dr.B для всех строк, прежде чем мы сможем вычислить значение dr.C для каждой строки. –

ответ

4

Работа, начатая Parallel.ForEach(), будет выполнена до ее возвращения. Внутренне, ForEach() порождает Task для каждой итерации и называет Wait(). В результате вам не нужно синхронизировать доступ между вызовами ForEach().

Вы сделать необходимости держать это в виде, для отдельных задач с ForEach() перегрузками, которые позволяют вам доступ к состоянию ШСА, агрегирование результатов от задач и т.д. Например, в этом тривиальном примере, который подводит итог 1 ≤ x ≤ 100, то Action передается localFinally из Parallel.For() должен быть обеспокоены вопросами синхронизации,

var total = 0; 

Parallel.For(0, 101,() => 0, // <-- localInit 
(i, state, localTotal) => { // <-- body 
    localTotal += i; 
    return localTotal; 
}, localTotal => { <-- localFinally 
    Interlocked.Add(ref total, localTotal); // Note the use of an `Interlocked` static method 
}); 

// Work of previous `For()` call is guaranteed to be done here 

Console.WriteLine(total); 

в вашем примере, не нужно вставить барьер памяти между ForEach() вызовов. В частности, петля IV может зависеть от завершенных результатов II, и Parallel.ForEach() уже вставлен III для вас.

Отрывок из источников: Parallel Framework and avoiding false sharing

+0

Спасибо. Когда я просматриваю код Parallel.ForEach на несколько уровней, это выглядит как «частный статический ParallelLoopResult ForWorker », который выполняет большую часть работы. Мне немного сложно следовать, но похоже, что есть вызов «rootTask.Wait()»; который ждет завершения всех рабочих потоков перед продолжением. Но даже несмотря на то, что мой основной поток ждет завершения работника, это не гарантирует, что рабочие потоки, распространенные среди всех других процессоров, обязательно будут видеть самые последние записи, когда они будут читать значения, не так ли? –

+0

Это правильно, и я отредактирую свой ответ, возможно, немного более понятным. Задачи, порожденные * тем же самым * 'ForEach()' вызовом, должны знать о проблемах с параллелизмом - обычное пятно, которое нужно беспокоиться, находится в 'Action', вы передаете' localFinally'. Однако разные вызовы 'ForEach()' могут безопасно зависеть от результатов предыдущих запросов ForEach() '. – jdphenix

+0

Я думаю, мой вопрос связан с этим ... http://stackoverflow.com/questions/6581848/memory-barrier-generators. Я просто хочу убедиться, что конец Parallel.ForEach попадает в один из этих ведер. Так что у него есть собственный MemoryBarrier (эффективно) и гарантирует, что все будет полностью записано до следующего запуска Parallel.ForEach. –

0

С более чем один поток будет доступ к той же переменной «dr.B», вам нужно будет убедиться, что ваш C# код потокобезопасно.

Попробуйте использовать "замок" круглый каждой операции https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

например,

private Object thisLock1 = new Object(); 
... 
lock(thisLock1) 
{ 
    dr.C = 2.0 * dr.B; 
} 

... 
lock(thisLock1) 
{ 
    dr.B = 2.0*dr.A; 
} 

Однако это приведет к победе в параллельной обработке. так как каждый поток должен ждать, пока не будет выполнен следующий.

Обязательно прочитайте потенциальную ловушку с параллельной обработкой: https://msdn.microsoft.com/en-us/library/dd997403%28v=vs.110%29.aspx

+0

В конкретном примере OP, использующем 'Parallel.ForEach()', каждый вызов 'ForEach()' уже обрабатывает синхронизацию, в частности, чтобы все параллельные операции, порожденные вызовом, были завершены до их возвращения. – jdphenix

+0

@jdphenix - можете ли вы указать ссылку (для моего образования)? Примечание. Microsoft MSDN показывает: Как написать цикл Parallel.ForEach, который имеет переменные на основе потока https://msdn.microsoft.com/en-us/library/dd460703%28v=vs.110%29.aspx, который использует (finalResult) => Interlocked.Add (ref total, finalResult) –

+0

Правильно указать, что отдельному 'ForEach()' необходимо учитывать безопасность потоков, и поэтому 'ForEach()' обеспечивает перегрузки, которые позволяют специфицировать поток локальный и финализатор, как вы связали. Поскольку 'Wait()' вызывает внутреннее значение для 'ForEach()', мне нужно было проверить источник ссылки, чтобы подтвердить это. – jdphenix

Смежные вопросы