Потокобезопасное использование элементов синглтона

StackOverflow https://stackoverflow.com/questions/42505

Вопрос

У меня есть одноэлементный класс C #, который используют несколько классов.Является ли доступ через Instance к тому Toggle() потокобезопасный метод?Если да, то с помощью каких предположений, правил и т.д.Если нет, то почему и как я могу это исправить?

public class MyClass
{
    private static readonly MyClass instance = new MyClass();

    public static MyClass Instance
    {
        get { return instance; }
    }

    private int value = 0;

    public int Toggle()
    {
        if(value == 0) 
        {
            value = 1; 
        }
        else if(value == 1) 
        { 
            value = 0; 
        }

        return value;
    }
}
Это было полезно?

Решение

Является ли доступ через 'Instance' к классу 'Toggle()' потокобезопасным?Если да, то с помощью каких предположений, правил и т.д.Если нет, то почему и как я могу это исправить?

Нет, это не потокобезопасно.

В принципе, оба потока могут запускать Toggle функционируют одновременно, так что это может произойти

    // thread 1 is running this code
    if(value == 0) 
    {
        value = 1; 
        // RIGHT NOW, thread 2 steps in.
        // It sees value as 1, so runs the other branch, and changes it to 0
        // This causes your method to return 0 even though you actually want 1
    }
    else if(value == 1) 
    { 
        value = 0; 
    }
    return value;

Вам нужно исходить из следующего предположения.

Если запущены 2 потока, они могут и будут чередоваться и взаимодействовать друг с другом случайным образом в любой момент.Вы можете быть на полпути к записи или чтению 64-битного целого числа или числа с плавающей запятой (на 32-битном процессоре), и другой поток может вмешаться и изменить его из-под вас.

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

Вы можете решить, что и где заблокировать, подумав о подобных вещах:

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

Еще раз взглянем на ваш пример

    // we read value here
    if(value == 0) 
    {
        value = 1; 
    }
    else if(value == 1) 
    { 
        value = 0; 
    }
    // and we return it here
    return value;

Для того чтобы это вернуло то, чего мы ожидаем, мы предполагаем, что value не изменится между чтением и return.Для того чтобы это предположение действительно было правильным, вам необходимо заблокировать value на время действия этого блока кода.

Чтобы ты сделал это:

lock( value )
{
     if(value == 0) 
     ... // all your code here
     return value;
}

ОДНАКО

В .NET вы можете блокировать только ссылочные типы.Int32 - это тип значения, поэтому мы не можем его заблокировать.
Мы решаем эту проблему, вводя "фиктивный" объект и блокируя это везде, где мы хотели бы зафиксировать "значение".

Это то , что Ben Scheirman имеется в виду.

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

Исходная реализация не является потокобезопасной, как указывает Бен

Простой способ сделать его потокобезопасным - ввести оператор lock.Например.вот так:

public class MyClass
{
    private Object thisLock = new Object();
    private static readonly MyClass instance = new MyClass();
    public static MyClass Instance
    {
        get { return instance; }
    }
    private Int32 value = 0;
    public Int32 Toggle()
    {
        lock(thisLock)
        {
            if(value == 0) 
            {
                value = 1; 
            }
            else if(value == 1) 
            { 
                value = 0; 
            }
            return value;
        }
    }
}

Именно так я и думал.Но я... я ищу подробности...'Toggle()' не является статическим методом, но является членом статического свойства (при использовании 'Instance').Это то, что делает его общим для потоков?

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

И как это применимо к синглетонам в целом.Должен ли я обращаться к этому в каждом методе моего класса?

Как я уже говорил выше, поскольку это синглтон, вы знаете, что разные потоки будут обращаться к одному и тому же объекту, возможно, в одно и то же время.Это не означает, что вы должны заставить каждый метод получать блокировку.Если вы заметили, что одновременный вызов может привести к повреждению состояния класса, то вам следует применить метод, упомянутый @Thomas

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

Нет.Ваш класс просто не является потокобезопасным.Синглтон не имеет к этому никакого отношения.

(Я начинаю понимать тот факт, что члены экземпляра, вызываемые статическим объектом, вызывают проблемы с потоковой передачей)

К этому это тоже не имеет никакого отношения.

Ты должен думать вот так:Возможно ли в моей программе для 2 (или более) потоков одновременный доступ к этому фрагменту данных?

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

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

private static object _lockDummy = new object();


...

lock(_lockDummy)
{
   //do stuff
}

Я бы также добавил защищенный конструктор в MyClass, чтобы компилятор не создавал общедоступный конструктор по умолчанию.

Я думал, что если я сброшу шаблон singleton и заставлю всех получить новый экземпляр класса, это облегчит некоторые проблемы...но это не мешает никому другому инициализировать статический объект этого типа и передавать его по кругу...или из-за запуска нескольких потоков, все из которых обращаются к 'Toggle()' из одного и того же экземпляра.

Бинго :-)

Теперь я понимаю.Это жестокий мир.Лучше бы я не занимался рефакторингом устаревшего кода:(

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

Ну, на самом деле я не настолько хорошо знаю C #...но я хорошо разбираюсь в Java, поэтому я дам ответ на этот вопрос, и, надеюсь, они достаточно похожи, чтобы это было полезно.Если нет, я приношу свои извинения.

Ответ таков: нет, это небезопасно.Один поток может вызывать Toggle() одновременно с другим, и возможно, хотя и маловероятно с этим кодом, что Thread1 может установить value в промежутке между тем, как Thread2 проверяет его, и тем, когда он его устанавливает.

Чтобы исправить, просто сделайте Toggle() synchronized.Он ничего не блокирует и не вызывает ничего, что могло бы породить другой поток, который мог бы вызвать Toggle() , так что это все, что вам нужно сделать, чтобы сохранить его.

Цитата:

if(value == 0) { value = 1; }
if(value == 1) { value = 0; }
return value;

value всегда будет равно 0...

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