Вопрос

Предположим, у меня есть переменная "counter", и есть несколько потоков, обращающихся к "counter" и устанавливающих значение с помощью Interlocked, т.е.:

int value = Interlocked.Increment(ref counter);

и

int value = Interlocked.Decrement(ref counter);

Могу ли я предположить, что изменение, внесенное Interlocked, будет видно во всех потоках?

Если нет, то что мне следует сделать, чтобы все потоки синхронизировали переменную?

Редактировать:кто-то предложил мне использовать volatile .Но когда я устанавливаю "счетчик" как volatile, появляется предупреждение компилятора "ссылка на поле volatile не будет обрабатываться как volatile".

Когда я читал онлайн-справку, там говорилось: "Изменчивое поле обычно не должно передаваться с использованием параметра ref или out".

Это было полезно?

Решение

Блокированное увеличение / уменьшение на процессорах x86 (добавление блокировки x86 / dec) автоматически создает барьер памяти который обеспечивает видимость для всех потоков (т. Е. все потоки могут видеть его обновление по порядку, например, последовательную согласованность памяти).Барьер памяти приводит к завершению всех ожидающих загрузки / сохранения данных в памяти. volatile не имеет отношения к этому вопросу, хотя C # и Java (и некоторые компиляторы C / C ++) применяют volatile чтобы создать барьер памяти.Но заблокированная операция уже имеет барьер памяти со стороны процессора.

Пожалуйста, также взгляните мой другой ответ в stackoverflow.

Обратите внимание, что я предположил, что C # InterlockedIncrement / Decrement являются внутренним отображением для x86 lock add / dec.

Другие советы

Могу ли я предположить, что изменение, внесенное Interlocked, будет видно во всех потоках?

Это зависит от того, как вы считываете значение.Если вы "просто" прочитаете это, то нет, это не всегда будет видно в других потоках, если вы не пометите это как изменяемое.Однако это вызывает раздражающее предупреждение.

В качестве альтернативы (и гораздо предпочтительнее IMO) прочтите его, используя другую взаимосвязанную инструкцию.При этом всегда будет отображаться обновленное значение во всех потоках:

int readvalue = Interlocked.CompareExchange(ref counter, 0, 0);

который возвращает прочитанное значение, и если оно было равно 0, заменяет его на 0.

Мотивация:предупреждение намекает на то, что что-то не так;объединение двух методов (volatile & interlocked) не было предполагаемым способом сделать это.

Обновить:похоже, что другой подход к надежному 32-разрядному чтению без использования "volatile" заключается в использовании Thread.VolatileRead как было предложено в этот ответ.Есть также некоторые доказательства того, что я совершенно не прав в использовании Interlocked например, для 32-разрядного чтения эта проблема с подключением, хотя я задаюсь вопросом, не является ли это различие немного педантичным по своей природе.

Что я действительно имею в виду, так это:не используйте этот ответ в качестве единственного источника;У меня есть свои сомнения на этот счет.

На самом деле это не так.Если вы хотите безопасно модифицировать counter, тогда вы поступаете правильно.Но если вы хотите почитать counter непосредственно вам нужно объявить это как volatile.В противном случае у компилятора нет оснований полагать, что counter изменится, потому что Interlocked операции находятся в коде, который он может не видеть.

Блокировка гарантирует, что только 1 поток одновременно может обновлять значение.Чтобы гарантировать, что другие потоки смогут прочитать правильное значение (а не кэшированное значение), пометьте его как изменяемое.

общедоступный счетчик изменчивых значений int;

НЕТ;ан Блокируемый только при записи один выполняет нет убедитесь, что операции чтения переменных в коде действительно свежие; программа, которая также неправильно считывает данные из поля возможно, он не является потокобезопасным, даже при "модели с сильной памятью".Это относится к любой форме присвоения полю, совместно используемому потоками.

Вот пример кода, который никогда не завершится из-за JIT.(Он был изменен с Барьеры памяти в .NET чтобы быть запущенной программой LINQPad, обновленной для ответа на вопрос).

// Run this as a LINQPad program in "Release Mode".
// ~ It will never terminate on .NET 4.5.2 / x64. ~
// The program will terminate in "Debug Mode" and may terminate
// in other CLR runtimes and architecture targets.
class X {
    // Adding {volatile} would 'fix the problem', as it prevents the JIT
    // optimization that results in the non-terminating code.
    public int terminate = 0;
    public int y;

    public void Run() {
        var r = new ManualResetEvent(false);
        var t = new Thread(() => {
            int x = 0;
            r.Set();
            // Using Volatile.Read or otherwise establishing
            // an Acquire Barrier would disable the 'bad' optimization.
            while(terminate == 0){x = x * 2;}
            y = x;
        });

        t.Start();
        r.WaitOne();
        Interlocked.Increment(ref terminate);
        t.Join();
        Console.WriteLine("Done: " + y);
    }
}

void Main()
{
    new X().Run();
}

Объяснение из Барьеры памяти в .NET:

На этот раз это JIT, а не аппаратное обеспечение. Ясно, что JIT кэшировал значение переменной terminate [в регистре EAX, и] программа теперь застряла в цикле, выделенном выше..

Либо используя lock или добавление Thread.MemoryBarrier внутри цикла while проблема будет устранена.Или вы даже можете использовать Volatile.Read [или volatile поле]. Целью барьера памяти здесь является только подавление JIT-оптимизации. Теперь, когда мы увидели, как программное и аппаратное обеспечение может изменять порядок операций с памятью, пришло время обсудить барьеры памяти ..

То есть дополнительный барьер конструкция требуется на стороне чтения, чтобы предотвращение проблем с компиляцией и переупорядочением / оптимизацией JIT: это другая проблема, чем когерентность памяти!

Добавление volatile здесь было бы предотвращать JIT-оптимизацию и, таким образом, "устранить проблему", даже если это приводит к предупреждению.Эта программа также может быть исправлена с помощью Volatile.Read или одна из различных других операций, которые вызывают барьер:эти барьеры являются такой же частью корректности программы CLR / JIT, как и лежащие в их основе аппаратные барьеры памяти.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top