Являются ли закрытые поля статического класса C # потокобезопасными?

2

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

  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;
        }
    }
  • 3
    Вы должны определить, что вы подразумеваете под «потокобезопасность». Единственная явная «безопасность потока», которая добавляется для статических полей с инициализаторами, заключается в том, что они инициализируются только один раз, до того, как они будут использованы, но кроме этого никакого волшебства со статическими полями не происходит, если вы явно не добавите его.
  • 0
    @LasseVågsætherKarlse LasseVågsætherKarlsen, под «потокобезопасным» я подразумеваю, что способ, которым я определил мой статический класс в Варианте 1, является правильным для того, чтобы его использовали, как я показал выше в классе Program.
Показать ещё 3 комментария
Теги:
class
multithreading
static
private

5 ответов

5

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

Пусть не хоронят здесь Леду

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

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

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

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

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

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

1

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

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

Ваша вторая версия предпочтительнее. Вы можете заблокировать его немного больше, сделав поле 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

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

0

из С# в глубину:

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

Экземпляр класса создан.

Все статические члены класса имеют ссылки.

Таким образом, оба ваших варианта работают, но вам не нужно блокировать конструктор, как во втором варианте.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню