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

С# переменная свежесть

Предположим, что у меня есть переменная-член в классе (с атомарным типом данных чтения/записи):

bool m_Done = false;

И позже я создаю задачу, чтобы установить ее в true:

Task.Run(() => m_Done = true);

Мне все равно, когда точно m_Done будет установлено значение true. Мой вопрос заключается в том, что у меня есть гарантия по спецификации языка С# и параллельной библиотеке задач, которая в конечном итоге m_Done будет истинным, если я получаю доступ к нему из другого потока?
Пример:

if(m_Done) { // Do something }

Я знаю, что использование блокировок приведет к появлению необходимых барьеров памяти, а m_Done будет отображаться как истина позже. Также я могу использовать Volatile.Write при настройке переменной и Volatile.Read when прочитав его. Я вижу много кода, написанного таким образом (без блокировок или изменчивости), и я не уверен, что это правильно.

Обратите внимание, что мой вопрос не нацелен на конкретную реализацию С# или .Net, он ориентирован на спецификацию. Мне нужно знать, будет ли текущий код работать так же, если он работает на x86, x64, Itanium или ARM.

4b9b3361

Ответ 1

Мне все равно, когда точно m_Done будет установлено значение true. Мой вопрос заключается в том, что у меня есть гарантия от спецификации языка С# и параллельной библиотеки задач, которая в конечном итоге m_Done будет истинна, если я получаю доступ к ней из другого потока?

Нет.

Чтение m_Done является энергонезависимым и поэтому может перемещаться произвольно далеко назад во времени, и результат может быть кэширован. В результате можно было бы наблюдать false за каждый прочитанный за все время.

Мне нужно знать, будет ли текущий код работать так же, если он работает на x86, x64, Itanium или ARM.

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

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

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

Кроме того, вопрос предполагает, что вы игнорируете существующие механизмы, которые определяют, что задача завершена, и вместо этого сворачивайте свои собственные. Существующие механизмы были разработаны экспертами; используйте их.

Я вижу много кода, написанного таким образом (без блокировок или volatile), и я не уверен, что это правильно.

Это почти наверняка не.

Хорошее упражнение для человека, написавшего этот код, таково:

static volatile bool q = false;
static volatile bool r = false;
static volatile bool s = false;
static volatile bool t = false;
static object locker = new object();

static bool GetR() { return r; }  // No lock!
static void SetR() { lock(locker) { r = true; } }

static void MethodOne()
{
  q = true;
  if (!GetR())
    s = true;
}

static void MethodTwo()
{
  SetR();
  if (!q)
    t = true;
}

После инициализации полей MethodOne вызывается из одного потока, MethodTwo вызывается из другого. Обратите внимание, что все нестабильно и что запись в r не только изменчива, но и полностью огорожена. Оба метода закончены нормально. Возможно ли впоследствии, когда s и t будут наблюдаться как истинное в первой нити? Возможно ли это на x86? Это не так; если первый поток выигрывает гонку, то t остается ложным, а если второй поток побеждает, то s остается ложным; этот анализ неверен. Зачем? (Подсказка: как разрешено x86 переписывать MethodOne?)

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

Ответ 2

попробуйте этот код, создайте выпуск, запустите без Visual Studio:

class Foo
{
    private bool m_Done = false;

    public void A()
    {
        Task.Run(() => { m_Done = true; });
    }

    public void B()
    {
        for (; ; )
        {
            if (m_Done)
                break;
        }

        Console.WriteLine("finished...");
    }
}

class Program
{

    static void Main(string[] args)
    {
        var o = new Foo();
        o.A();
        o.B();

        Console.ReadKey();
    }
}

у вас есть хороший шанс увидеть, как он работает навсегда