Ориентированная на многопотоковое исполнение инициализация статических переменных

Я использовал этот шаблон для инициализации статических данных в моих классах. Это выглядит ориентированным на многопотоковое исполнение мне, но я знаю, как тонкие проблемы многопоточности могут быть. Вот код:

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

Есть ли здесь какие-либо дыры? Возможно, существует более простой способ сделать это.

ОБНОВЛЕНИЕ: Согласие, кажется, что статический конструктор является способом пойти. Я придумал следующую версию с помощью статического конструктора.

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

Я надеюсь, что это лучше.

15
задан RaoulRubin 4 June 2010 в 04:15
поделиться

5 ответов

Вы можете использовать статические конструкторы для инициализации статических переменных, которые, как гарантирует C#, будут вызваны только один раз в пределах каждого AppDomain. Не уверен, что вы их рассматривали.

Итак, вы можете прочитать следующее: http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx (Static Constructors)

And this: Является ли статический конструктор C# потокобезопасным?

12
ответ дан 1 December 2019 в 03:24
поделиться

Выполнение lock() на _myResource и изменение его внутри оператора lock() кажется плохой идеей. Рассмотрим следующий рабочий процесс:

  1. поток 1 вызывает MyClass().
  2. выполнение останавливается перед строкой _init = true; сразу после присвоения _myResource.
  3. процессор переключается на поток 2.
  4. поток 2 вызывает MyClass(). Поскольку _init все еще false и ссылка _myResource изменилась, он успешно входит в блок оператора lock().
  5. _init по-прежнему false, поэтому поток 2 переназначает _myResource.

Обходной путь: создать статический объект и заблокировать этот объект вместо инициализированного ресурса:

private static readonly object _resourceLock = new object();

/*...*/

lock(_resourceLock)
{
    /*...*/
}
6
ответ дан 1 December 2019 в 03:24
поделиться

Ваш класс небезопасен:

  1. Вы меняете объект, который блокируете, после того, как заблокировали его.
  2. У вас есть свойство, которое получает ресурс, не блокируя его.
  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;
        } 
    }
}

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

3
ответ дан 1 December 2019 в 03:24
поделиться

В зависимости от вашего случая использования (т.е. если потокам не нужно передавать информацию друг другу с помощью этой переменной),пометка переменной-члена как [ThreadStatic] может быть решением.
См. здесь.

1
ответ дан 1 December 2019 в 03:24
поделиться
static string _myResource = "";
...
public MyClass()
{
    ...
    lock (_myResource)
    {
    }
}

Из-за интернирования строки вам не следует блокировать строковый литерал. Если вы заблокируете строковый литерал, и этот строковый литерал используется несколькими классами, вы можете использовать эту блокировку совместно. Это потенциально может вызвать неожиданное поведение.

0
ответ дан 1 December 2019 в 03:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: