Являются ли закрытые поля статического класса C # потокобезопасными? - PullRequest
0 голосов
/ 20 декабря 2018

У меня есть статический класс C #, доступный из нескольких потоков.Два вопроса:

  1. Защищены ли мои частные статические поля потоком при инициализации поля в объявлении?
  2. Должен ли я блокироваться при создании частных статических полей из статического конструктора?

Использование статического класса из разных потоков:

class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                Task.Run(() =>
                {
                    string name = MyStaticClass.GetValue(9555);
                    //...
                });
            }
        }
}

Опция 1 статического класса:

public static class MyStaticClass
    {
        private static MyClass _myClass = new MyClass();

        public static string GetValue(int key)
        {
            return _myClass.GetValue(key);
        }
    }

Опция 2 статического класса:

public static class MyStaticClass
    {
        private static MyClass _myClass;
        private static object _lockObj = new object();

        static MyStaticClass()
        {
            InitMyClass();
        }

        private static void InitMyClass()
        {
            if (_myClass == null)
            {
                lock(_lockObj)
                {
                    if (_myClass == null)
                    {
                        _myClass = new MyClass();
                    }
                }
            }
        }

        public static string GetValue(int key)
        {
            return _myClass.GetValue(key);
        }
    }

Класс экземпляра, созданный из статического класса:

public class MyClass
    {
        private Dictionary<int, Guid> _valuesDict = new Dictionary<int, Guid>();

        public MyClass()
        {
            for (int i = 0; i < 10000; i++)
            {
                _valuesDict.Add(i, Guid.NewGuid());
            }
        }

        public string GetValue(int key)
        {
            if (_valuesDict.TryGetValue(key, out Guid value))
            {
                return value.ToString();
            }

            return string.Empty;
        }
    }

Ответы [ 4 ]

0 голосов
/ 20 декабря 2018

Должен ли я блокировать при инициализации частных статических полей из статического конструктора?

Давайте не будем хоронить лиду здесь:

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

Это особый случай более общего совета: никогда не делайте ничего сложного с потоками в статическомконструктор .Тот факт, что статические конструкторы эффективно заблокированы и эта блокировка может быть оспорена любым кодом, который обращается к вашему типу, означает, что вы можете очень быстро попасть в тупики, которые вы не ожидали и которые трудно увидеть.Я привожу пример здесь: https://ericlippert.com/2013/01/31/the-no-lock-deadlock/

Если вы хотите отложенной инициализации, используйте конструкцию Lazy<T>;оно было написано экспертами, которые знают, как сделать его безопасным.

Защищены ли мои частные статические поля потоком, когда поле инициализируется в объявлении?

Безопасность потока - этосохранение программных инвариантов при вызове программных элементов из нескольких потоков.Вы не сказали, каковы ваши инварианты, поэтому невозможно сказать, является ли ваша программа «безопасной».

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

0 голосов
/ 20 декабря 2018

Оба верны, но нет необходимости lock внутри static constructor.Итак, я выберу первый вариант, он короче и понятнее

0 голосов
/ 20 декабря 2018

Ваша вторая версия предпочтительнее.Вы можете заблокировать его немного больше, сделав свое поле readonly:

public static class MyStaticClass
{
    private static readonly MyClass _myClass = new MyClass();

    public static string GetValue(int key)
    {
        return _myClass.GetValue(key);
    }
}

Похоже, ваше намерение состоит в том, что _myClass изначально имеет значение MyClass и никогда не устанавливается другим,readonly выполняет это, указывая, что его можно установить только один раз, либо в статическом конструкторе, либо инициализируя его, как указано выше.Мало того, что другой поток не может установить его, но любая попытка изменить его приведет к ошибке компилятора.

Вы можете опустить readonly и просто никогда не установить _myClass снова, но readonly и связывает, иусиливает ваше намерение.

Вот где это становится сложнее: Ваша ссылка на экземпляр MyClass является поточно-ориентированной.Вам не нужно беспокоиться о том, будут ли различные потоки заменять его другим экземпляром (или устанавливать его на ноль), и он будет создан до того, как какой-либо поток попытается с ним взаимодействовать.

Что это делает не сделать это сделать MyClass потокобезопасным.Не зная, что он делает или как вы взаимодействуете с ним, я не могу сказать, что это за потребности или проблемы.

Если это проблема, один из подходов состоит в использовании lock для предотвращения одновременногодоступ, который не должен происходить, именно так, как продемонстрировал @ Mahi1722.Я включил код из этого ответа (не для плагиата, но если что-то случится с этим ответом, то этот будет ссылаться на ответ, который не существует.)

public static class MyStaticClass
{
    private static MyClass _myClass = new MyClass();
    private static object _lockObj = new object();

    public static string GetValue(int key)
    {
        return _myClass.GetValue(key);
    }

    public static void SetValue(int key)
    {
        lock(_lockObj)
        {           
             _myClass.SetValue(key);
        }
    }
}

Оба метода, которые взаимодействуют с_myClass блокировка с использованием _lockObject, что означает, что любое выполнение любого из них будет блокироваться, пока выполняется другой поток.

Это правильный подход.Другой способ - сделать поток MyClass потокобезопасным, либо используя параллельные коллекции, либо реализуя такие блокировки в этом классе.Таким образом, вам не нужно использовать операторы lock в каждом классе, который использует экземпляр MyClass.Вы можете просто использовать его, зная, что он управляет этим внутри.

0 голосов
/ 20 декабря 2018
Поля

статического класса по умолчанию не являются потокобезопасными, и их следует избегать, если только они не предназначены для чтения.Здесь также есть «блокировка», которая создает сериализованную обработку в многопоточной среде.

public static class MyStaticClass
{
    private static MyClass _myClass;
    private static object _lockObj;

    static MyStaticClass()
    {
           _myClass = new MyClass();
           _lockObj = new object();
    }

    public static string GetValue(int key)
    {
        return _myClass.GetValue(key);
    }
    public static void SetValue(int key)
    {
        lock(_lockObj)
        {           
             _myClass.SetValue(key);
        }
    }
}
...