Доступ к переменной в C # является атомарной операцией? - PullRequest
63 голосов
/ 13 августа 2008

Я был убежден, что если несколько потоков могут обращаться к переменной, то все операции чтения и записи в эту переменную должны быть защищены кодом синхронизации, например оператором «блокировки», поскольку процессор может переключиться на другую. поток на полпути через запись.

Однако я просматривал System.Web.Security.Membership с помощью Reflector и нашел код, подобный этому:

public static class Membership
{
    private static bool s_Initialized = false;
    private static object s_lock = new object();
    private static MembershipProvider s_Provider;

    public static MembershipProvider Provider
    {
        get
        {
            Initialize();
            return s_Provider;
        }
    }

    private static void Initialize()
    {
        if (s_Initialized)
            return;

        lock(s_lock)
        {
            if (s_Initialized)
                return;

            // Perform initialization...
            s_Initialized = true;
        }
    }
}

Почему поле s_Initialized читается вне блокировки? Не может ли другой поток одновременно пытаться писать в него? Является ли чтение и запись переменных атомарными?

Ответы [ 16 ]

0 голосов
/ 28 февраля 2013

Проверка If (itisso) { на логическое значение является атомарной, но даже если она не была нет необходимости блокировать первую проверку.

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

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

0 голосов
/ 31 августа 2012

Чтобы ваш код всегда работал на слабо упорядоченных архитектурах, вы должны установить MemoryBarrier перед тем, как писать s_Initialized.

s_Provider = new MemershipProvider;

// MUST PUT BARRIER HERE to make sure the memory writes from the assignment
// and the constructor have been wriitten to memory
// BEFORE the write to s_Initialized!
Thread.MemoryBarrier();

// Now that we've guaranteed that the writes above
// will be globally first, set the flag
s_Initialized = true;

Запись в память, которая происходит в конструкторе MembershipProvider, и запись в s_Provider не гарантируется до того, как вы запишете в s_Initialized на слабо упорядоченном процессоре.

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

РЕДАКТИРОВАТЬ: На самом деле, я смешиваю платформы в своих заявлениях. В C # спецификация CLR требует, чтобы записи были видны глобально, по порядку (используя дорогие инструкции для каждого магазина, если это необходимо). Следовательно, вам не нужно иметь этот барьер памяти там. Однако, если бы это был C или C ++, где такой гарантии глобального порядка видимости не существует, и ваша целевая платформа может иметь слабо упорядоченную память, и она является многопоточной, то вам нужно убедиться, что записи конструкторов видны глобально, прежде чем обновлять s_Initialized , который тестируется вне замка.

0 голосов
/ 13 августа 2008

Ack, неважно ... как указывалось, это действительно неверно. Это не мешает второму потоку войти в раздел кода «initialize». Ба.

Вы также можете украсить s_Initialized ключевым словом volatile и полностью отказаться от использования блокировки.

0 голосов
/ 13 августа 2008

Возможно Блокировка дает подсказку. А в остальном этот я довольно хорош.

Я бы догадался, что они не атомарные.

0 голосов
/ 13 августа 2008

То, что вы спрашиваете, является ли доступ к полю в методе, многократно атомарном - на что ответ нет.

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

private static void Initialize()
{
    if (s_Initialized)
        return;

    lock(s_lock)
    {
        if (s_Initialized)
            return;
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}
0 голосов
/ 13 августа 2008

Я думал, что они были - я не уверен в точке блокировки в вашем примере, если вы не делаете что-то одновременно с s_Provider - тогда блокировка будет гарантировать, что эти вызовы произошли вместе.

Это //Perform initialization комментарий охватывает создание s_Provider? Например

private static void Initialize()
{
    if (s_Initialized)
        return;

    lock(s_lock)
    {
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}

В противном случае это статическое свойство get в любом случае просто вернет ноль.

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