Потоковое безопасное использование одноэлементных элементов
У меня есть один одиночный класс С#, который используют несколько классов. Является ли доступ через 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;
}
}
Ответы
Ответ 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(), так что все, что вам нужно сделать, сохранить его.