Требуется ли для одновременной блокировки и чтения барьер памяти или блокировка? - PullRequest
3 голосов
/ 23 марта 2012

Это простая проблема, но после прочтения Зачем мне нужен барьер памяти? Я очень смущен этим.

В приведенном ниже примере предположим, что разные потоки многократно вызывают Increment и Counter:

class Foo{
    int _counter=0;
    public int Counter 
    {
        get { return _counter; }
    }

    public void Increment()
    {
        Interlocked.Increment(ref _counter);
    }
}

Извините, если я неверно истолковываю Зачем мне нужен барьер памяти? , но кажется, что он предполагает, что приведенный выше класс может не обеспечивать гарантию свежести при чтении значения _counter. Может ли поток, который повторно обращается к свойству Counter, навсегда застрять в старом значении Counter (потому что он кэшируется в регистре)?

Необходим ли барьер памяти или блокировка до return _counter;?

1 Ответ

7 голосов
/ 23 марта 2012

Барьер памяти или блокировка перед возвратом _counter; необходимо

Да, абсолютно. Рассмотрим следующий код.

while (foo.Counter == 0)
{
  // Do something
}

Проблема в том, что если содержимое внутри цикла достаточно простое, то компилятор C #, JIT или аппаратное обеспечение будут оптимизировать код таким образом.

int register = foo._counter;
while (register == 0)
{
  // Do something
}

Или даже это.

if (foo._counter == 0)
{
  START: 
  // Do something
  goto START;
}

Обратите внимание, что я использую _counter вместо Counter, чтобы предположить, что свойство, вероятно, будет встроенным. Затем, что более важно, JIT-компилятор может «поднять» чтение _counter за пределы цикла, чтобы оно читалось только один раз.

Барьеры памяти не дают гарантии свежести как таковые. Они предотвращают определенные программные или аппаратные оптимизации, которые изменяют порядок чтения и записи в память. гарантия свежести - это скорее побочный эффект.

Итак, чтобы обернуть вещи, ваше свойство Counter должно выглядеть следующим образом.

public int Counter 
{
    get { return Interlocked.CompareExchange(ref _counter, 0, 0); }
}
...