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

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

У меня есть один одиночный класс С#, который используют несколько классов. Является ли доступ через 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;
    }
}
4b9b3361

Ответ 1

Доступ через "Экземпляр" к классу "Toggle()" threadsafe? Если да, по каким предположениям, правилам и т.д. Если нет, почему и как я могу это исправить?

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

В принципе, оба потока могут одновременно запускать функцию 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-битное целое число или float (на 32-битном процессоре), а другой поток может вскакивать и изменять его из-под вас.

Если 2 потока никогда не имеют доступа к чему-либо вообще, это не имеет значения, но как только они это сделают, вы должны помешать им наступать друг на друга. Способ сделать это в .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 - это тип значения, поэтому мы не можем его заблокировать.
Мы решим это, введя объект 'dummy' и блокируя это, где бы мы не захотели блокировать "значение".

Это то, о чем говорит Бен Шейрман.

Ответ 2

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

Простым способом обеспечения безопасности потока является введение оператора блокировки. Например. например:

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;
        }
    }
}

Ответ 3

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

Если ваше приложение многопоточно, и вы можете видеть, что несколько потоков будут обращаться к этому методу, что делает его общим для потоков. Поскольку ваш класс является Синглтоном, вы знаете, что другой поток будет иметь доступ к ИСПОЛЬЗУЕМОМУ объекту, поэтому будьте внимательны к безопасности потоков ваших методов.

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

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

Ответ 4

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

private static object _lockDummy = new object();


...

lock(_lockDummy)
{
   //do stuff
}

Ответ 5

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

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

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

Это тоже не имеет никакого отношения.

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

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

Ответ 6

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

Ответ 7

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

Бинго: -)

Я понял это сейчас. Это сложный мир. Мне жаль, что я не рефакторинг устаревшего кода: (

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

Ответ 8

Цитата:

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

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

Ответ 9

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

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

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