C# static class 私有字段线程安全吗?

Are C# static class private fields thread safe?

我有一个从多个线程访问的 C# 静态 class。两个问题:

  1. 在声明时初始化字段时,我的私有静态字段线程安全吗?
  2. 从静态构造函数中创建私有静态字段时应该锁定吗?

来自不同线程的静态class用法:

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

静态选项 1 class:

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

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

静态选项 2 class:

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

从静态 class 创建的实例 class:

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

静态字段 class 默认情况下不是线程安全的,应该避免,除非它只是为了读取目的。 这里的缺点也是"lock",它将在多线程环境中创建序列化处理。

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

两者都正确, 但是static constructor里面没有必要lock。 所以,我会选择第一个选项,它更短更清晰

你的第二个版本更可取。您可以通过将字段设为 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 线程安全,通过使用并发集合或在 class 中实现此类锁。这样您就不必在每个使用 MyClass 实例的 class 中使用 lock 语句。您可以在知道它在内部进行管理的情况下使用它。

Should I lock when initializing private static fields from within static constructor?

我们不要在这里埋葬 lede:

永远不要锁定静态构造函数。静态构造函数已被框架锁定,因此它们 运行 在一个线程上恰好一次。

这是一个更通用的好建议的特例:永远不要在静态构造函数中对线程做任何花哨的事情。事实上,静态构造函数被有效地锁定,并且任何访问您的类型的代码都可以争用该锁定,这意味着您可以很快陷入您没有预料到并且很难看到的死锁。我这里举个例子:https://ericlippert.com/2013/01/31/the-no-lock-deadlock/

如果要延迟初始化,请使用 Lazy<T> 构造;它是由知道如何确保安全的专家编写的。

Are my private static fields thread safe when the field is initialized on declaration?

线程安全是当程序元素被多线程调用时程序不变量的保存。你还没有说你的不变量是什么,所以不可能说你的程序是"safe".

如果您担心的不变量是静态构造函数在执行第一个静态方法或创建第一个实例之前观察到 运行,C# 可以保证这一点。当然,如果您在静态构造函数中编写了疯狂的代码,那么疯狂的事情就会发生,所以再次尝试让您的静态构造函数非常简单。