Потокобезопасное использование членов синглтона - PullRequest
15 голосов
/ 04 сентября 2008

У меня есть одноэлементный класс 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;
    }
}

Ответы [ 9 ]

28 голосов
/ 04 сентября 2008

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

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

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

8 голосов
/ 04 сентября 2008

Оригинальная реализация не является поточно-ориентированной, как указывает Бен

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

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;
        }
    }
}
2 голосов
/ 04 сентября 2008

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

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

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

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

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

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

2 голосов
/ 04 сентября 2008

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

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

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

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

2 голосов
/ 04 сентября 2008

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

private static object _lockDummy = new object();


...

lock(_lockDummy)
{
   //do stuff
}
1 голос
/ 04 сентября 2008

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

Бинго: -)

Теперь я понял. Это сложный мир. Жаль, что я не рефакторинг старого кода: (

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

1 голос
/ 04 сентября 2008

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

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

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

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

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

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

Цитата:

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

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

...