Поточно-безопасное использование членов синглтона

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

Нужен ли частный ctor, чтобы быть действительно синглтоном?

gmail user 26.08.2015 16:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
15
1
6 698
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Цитировать:

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

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

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

private static object _lockDummy = new object();


...

lock(_lockDummy)
{
   //do stuff
}

но вместо использования (_lockDummy ()) вы имеете в виду блокировку (_lockDummy)

Dave Cameron 19.11.2009 04:25

Нет, не исправлено. Он по-прежнему читает using(_lockDummy). Он должен читать lock(_lockDummy).

AndrewJacksonZA 24.02.2010 10:10

Хорошо, на этот раз это действительно так :)

Ben Scheirman 24.02.2010 22:29

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

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

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

Как отмечает Бен, исходная реализация не является потокобезопасной.

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

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

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

Ответ принят как подходящий

Is access through 'Instance' to the 'Toggle()' class threadsafe? If yes, by what assumptions, rules, etc. If no, why and how can I fix it?

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

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

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

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

Phil Cooper 11.12.2015 15:15

That is what I thought. But, I I'm looking for the details... 'Toggle()' is not a static method, but it is a member of a static property (when using 'Instance'). Is that what makes it shared among threads?

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

And how does this apply to singletons in general. Would I have to address this in every method on my class?

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

Can I assume that the singleton pattern exposes my otherwise lovely thread-safe class to all the thread problems of regular static members?

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

(I'm getting my head around the fact that instance members called on a static object cause threading problems)

Это тоже не при чем.

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

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

I was thinking that if I dump the singleton pattern and force everyone to get a new instance of the class it would ease some problems... but that doesn't stop anyone else from initializing a static object of that type and passing that around... or from spinning off multiple threads, all accessing 'Toggle()' from the same instance.

Бинго :-)

I get it now. It's a tough world. I wish I weren't refactoring legacy code :(

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

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