Подтвердить что ты не робот

Когда использовать "volatile" или "Thread.MemoryBarrier()" в потоковом коде блокировки? (С#)

Когда я должен использовать volatile/Thread.MemoryBarrier() для безопасности потоков?

4b9b3361

Ответ 1

Вы используете volatile/Thread.MemoryBarrier(), когда хотите получить доступ к переменной через потоки без блокировки.

Переменные, которые являются атомарными, например, int, всегда читаются и записываются целиком одновременно. Это означает, что вы никогда не получите половину значения, прежде чем другой поток изменит его, а другую половину после его изменения. Из-за этого вы можете безопасно читать и записывать значение в разных потоках без синхронизации.

Однако компилятор может оптимизировать некоторые чтения и записи, которые вы предотвращаете с помощью ключевого слова volatile. Если вы, например, имеете такой цикл:

sum = 0;
foreach (int value in list) {
   sum += value;
}

Компилятор может фактически выполнять вычисления в регистре процессора и только записывать значение в переменную sum после цикла. Если вы создаете переменную sum volatile, компилятор будет генерировать код, который считывает и записывает переменную для каждого изменения, так что это значение обновляется на протяжении всего цикла.

Ответ 2

Что случилось с

private static readonly object syncObj = new object();
private static int counter;

public static int NextValue()
{
    lock (syncObj)
    {
        return counter++;
    }
}

?

Это делает все необходимые блокировки, барьеры памяти и т.д. для вас. Это хорошо понятное и читаемое, чем любой пользовательский код синхронизации на основе volatile и Thread.MemoryBarrier().


ИЗМЕНИТЬ

Я не могу думать о сценарии, в котором я бы использовал volatile или Thread.MemoryBarrier(). Например

private static volatile int counter;

public static int NextValue()
{
    return counter++;
}

не эквивалентен вышеприведенному коду и не потокобезопасен (volatile не делает ++ волшебным образом потокобезопасным).

В таком случае:

private static volatile bool done;

void Thread1()
{
    while (!done)
    {
        // do work
    }
}

void Thread2()
{
    // do work
    done = true;
}

(который должен работать) Я бы использовал ManualResetEvent, чтобы сигнализировать, когда Thread2 сделан.

Ответ 3

В принципе, если вы используете какую-либо другую синхронизацию, чтобы сделать ваш код потокобезопасным, вам не нужно.

Большинство механизмов блокировки (включая блокировку) автоматически подразумевают барьер памяти, так что несколько процессоров могут получить правильную информацию.

Volatile и MemoryBarrier в основном используются в сценариях без блокировки, где вы пытаетесь избежать штрафа за блокировку.

Изменить: Вы должны прочитать эту статью Джо Даффи о модели памяти CLR 2.0, в ней разъясняется много вещей (если вам действительно интересно, вы должны прочитать ВСЕ статью от Джо Даффи, которая по большому счету является самым экспертом в parallelism в .NET)

Ответ 4

Поскольку название подразумевает volatile гарантирует, что значение кэша будет сброшено в память, так что все потоки будут видеть одно и то же значение. Например, если у меня есть целое число, последняя запись которого сохраняется в кеше, другие потоки могут этого не видеть. Они могут даже увидеть свою кеш-копию этого целого. Маркировка переменной как изменчивой позволяет ее считывать непосредственно из памяти.

Шриванта Шри Аравинда Аттанайяке