Когда использовать 'volatile' или 'Thread.MemoryBarrier ()' в поточно-безопасном коде блокировки? (С #) - PullRequest
16 голосов
/ 25 августа 2009

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

Ответы [ 4 ]

20 голосов
/ 26 августа 2009

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

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

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

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

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

10 голосов
/ 26 августа 2009

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

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

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

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

10 голосов
/ 26 августа 2009

Что не так с

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

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

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


EDIT

Я не могу вспомнить сценарий, в котором я бы использовал 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.

0 голосов
/ 04 сентября 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...