2017-01-31 6 views
4

У меня есть зубчатый массив double[][], который может быть изменен одновременно несколькими потоками. Я хотел бы сделать его потокобезопасным, но, если возможно, без блокировок. Потоки могут хорошо ориентироваться на один и тот же элемент в массиве, поэтому возникает вся проблема. Я нашел код для увеличения значения двойного атомарно используя Interlocked.CompareExchange метод: Why is there no overload of Interlocked.Add that accepts Doubles as parameters?Параллельная модификация двойных [] [] элементов без блокировки

Моего вопрос: будет ли она остаться атомной, если есть неровная ссылка на массив в Interlocked.CompareExchange? Ваше понимание очень ценится.

С примером:

public class Example 
    { 
     double[][] items; 

     public void AddToItem(int i, int j, double addendum) 
     { 
      double newCurrentValue = items[i][j]; 
      double currentValue; 
      double newValue; 
      SpinWait spin = new SpinWait(); 

      while (true) { 
       currentValue = newCurrentValue; 
       newValue = currentValue + addendum; 
       // This is the step of which I am uncertain: 
       newCurrentValue = Interlocked.CompareExchange(ref items[i][j], newValue, currentValue); 
       if (newCurrentValue == currentValue) break; 
       spin.SpinOnce(); 
      } 
     } 
    } 
+0

Лично Я бы переименовал 'newCurrentValue' в' oldValue' - это просто сбивает с толку :) –

+0

Вы понимаете, что while() в основном совпадает с использованием SpinLock, но с дополнительной сложностью? – Gusman

+0

@ Гусман вам нужен петля, хотя вам нужно повторить с начала в (редком) случае столкновения –

ответ

4

Да, она все равно будет атомной и потокобезопасно. Любые вызовы в одну и ту же ячейку будут передавать один и тот же адрес в два. Такие данные, как массив в виде поля на объекте, не имеют значения.

Однако линия:

double newCurrentValue = items[i][j]; 

не является атомарной - что может в теории дать разорванное значение (особенно на x86). В этом случае на самом деле все нормально, потому что в сценарии с порванным значением он просто попадает в цикл, считается столкновением и повторением - на этот раз с использованием известного атомного значения от CompareExchange.

+0

Большое спасибо, сэр. –

3

Кажется, что вы хотите в конечном итоге добавить некоторое значение в элемент массива.

Я полагаю, что есть только обновления значений (массив сам остается одной и той же частью памяти), и все обновления выполняются с помощью этого метода AddToItem.
Итак, вы должны каждый раз читать обновленное значение (в противном случае вы потеряли изменения, сделанные другим потоком, или получите бесконечный цикл).

public class Example 
{ 
    double[][] items; 

    public void AddToItem(int i, int j, double addendum) 
    { 
     var spin = new SpinWait(); 

     while (true) 
     { 
      var valueAtStart = Volatile.Read(ref items[i][j]); 
      var newValue = valueAtStart + addendum; 

      var oldValue = Interlocked.CompareExchange(ref items[i][j], newValue, valueAtStart); 

      if (oldValue.Equals(valueAtStart)) 
       break; 
      spin.SpinOnce(); 
     } 
    } 
} 


Обратите внимание, что мы должны использовать некоторые летучий метод для чтения items[i][j]. Volatile.Read используется для предотвращения нежелательных оптимизаций, которые разрешены в модели памяти .NET (см. Спецификации ECMA-334 и ECMA-335).
Поскольку мы обновляем значение атомарно (через Interlocked.CompareExchange), достаточно прочитать items[i][j] через Volatile.Read.

Если не все изменения этого массива делается в этом методе, то лучше написать цикл, в котором создать локальную копию массива, изменять и обновлять ссылку на новый массив (используя Volatile.Read и Interlocked.CompareExchange)

+0

И здесь нет особого смысла в SpinLock. В большинстве случаев цикл сам по себе более чем достаточно. –

+0

Большое спасибо за ваши предложения! –