Инициализация потоков статических переменных
Я использовал этот шаблон для инициализации статических данных в своих классах. Для меня это выглядит потокобезопасно, но я знаю, как могут возникать проблемы с тонкой резьбой. Здесь код:
public class MyClass // bad code, do not use
{
static string _myResource = "";
static volatile bool _init = false;
public MyClass()
{
if (_init == true) return;
lock (_myResource)
{
if (_init == true) return;
Thread.Sleep(3000); // some operation that takes a long time
_myResource = "Hello World";
_init = true;
}
}
public string MyResource { get { return _myResource; } }
}
Здесь есть какие-то отверстия? Возможно, есть более простой способ сделать это.
UPDATE: Консенсус, похоже, заключается в том, что статический конструктор - это путь. Я придумал следующую версию, используя статический конструктор.
public class MyClass
{
static MyClass() // a static constructor
{
Thread.Sleep(3000); // some operation that takes a long time
_myResource = "Hello World";
}
static string _myResource = null;
public MyClass() { LocalString = "Act locally"; } // an instance constructor
// use but don't modify
public bool MyResourceReady { get { return _myResource != null; } }
public string LocalString { get; set; }
}
Я надеюсь, что это лучше.
Ответы
Ответ 1
Вы можете использовать статические конструкторы для инициализации ваших статических переменных, которые гарантируют С# только один раз в каждом AppDomain. Не уверены, считали ли вы их.
Итак, вы можете прочитать это: http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx (Статические конструкторы)
И это: Безопасен ли поток статического конструктора С#?
Ответ 2
Выполнение lock()
on _myResource
и изменение его внутри оператора lock()
кажется плохой идеей.
Рассмотрим следующий рабочий процесс:
- поток 1 вызывает
MyClass()
.
- выполнение выполняется перед строкой
_init = true;
сразу после назначения _myResource
.
- процессор переключается на поток 2.
- поток 2 вызывает
MyClass()
. Поскольку _init
по-прежнему false
и refrence _myResource
изменен, он успешно входит в блок lock()
.
-
_init
по-прежнему false
, поэтому поток 2 переназначает _myResource
.
Обходной путь: создайте статический object
и заблокируйте этот объект вместо инициализированного ресурса:
private static readonly object _resourceLock = new object();
/*...*/
lock(_resourceLock)
{
/*...*/
}
Ответ 3
Ваш класс небезопасен:
- Вы меняете объект, который вы блокируете, после того, как вы заблокировали его.
- У вас есть свойство, которое получает ресурс без его блокировки.
- Вы блокируете примитивный тип, что обычно не является хорошей практикой.
Это должно сделать это за вас:
public class MyClass
{
static readonly object _sync = new object();
static string _myResource = "";
static volatile bool _init = false;
public MyClass()
{
if (_init == true) return;
lock (_sync)
{
if (_init == true) return;
Thread.Sleep(3000); // some operation that takes a long time
_myResource = "Hello World";
_init = true;
}
}
public string MyResource
{
get
{
MyClass ret; // Correct
lock(_sync)
{
ret = _myResource;
}
return ret;
}
}
}
Update:
Правильно, статический ресурс не должен возвращаться напрямую... Я скорректировал свой пример соответственно.
Ответ 4
В зависимости от вашего варианта использования (т.е. если потокам не нужно передавать информацию друг другу с помощью этой переменной), обозначение переменной-члена как [ThreadStatic]
может быть решением.
См. здесь.
Ответ 5
static string _myResource = "";
...
public MyClass()
{
...
lock (_myResource)
{
}
}
Из-за прерывания строки вы не должны блокировать строковый литерал.
ОБНОВЛЕНИЕ
Да, @RaoulRubin, поэтому, если вы заблокируете строковый литерал и этот строковый литерал используется несколькими классами, вы будете использовать эту блокировку. Это может привести к неожиданному поведению.