【问题标题】:C# static field, non thread, race conditionC# 静态字段、非线程、竞争条件
【发布时间】:2021-10-15 23:31:20
【问题描述】:

我想知道在以下示例中首先初始化了什么
A.aB.b

class A {
    static int a = methodA();
    static int methodA() { return 5; }
}
class B {
    static int b = methodB();
    static int methodB() { return A.a; } // here is the problem: `b` depends on `a`
}

如果a,则一切正常
methodA 返回 5,该值存储到 a。然后methodB检索a的值并返回。

但是如果b是第一个,它会从a获取垃圾值,然后a被初始化为5。

【问题讨论】:

  • 你试过了吗?设置一些断点并尝试不同的组合......最好的学习方式。您应该会发现第一次访问静态类时,它会初始化它并运行静态构造函数(如果存在)。因此,如果B.b 被调用并且A 尚未被访问,它应该立即初始化A
  • @David784 我试过了。它成功了。这并不意味着它会一直有效

标签: c# static


【解决方案1】:

在查看 c# 语言规范时,我看不到任何竞争条件。这是static constructors部分的引述:

要初始化一个新的封闭类类型,首先要为该特定封闭类型创建一组新的静态字段(静态和实例字段)。每个静态字段都被初始化为其默认值(默认值)。接下来,为那些静态字段执行静态字段初始化器(静态字段初始化)。最后执行静态构造函数。

上面的代码示例中发生的是“静态字段初始化”。规范列出了在默认值初始化之后和调用任何静态构造函数之前立即发生的情况。

以如下代码为例:

using System;

namespace console1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("begin main()");
            var x = B.b;
            Console.WriteLine($"x = {x}");
        }
    }

    public class B
    {
        static B()
        {
            Console.WriteLine("static B()");
        }
        public static int b = methodB();
        static int methodB()
        {
            Console.WriteLine($"initial B.b value {b}");
            Console.WriteLine("B.methodB()");
            return A.a;
        }
    }
    public class A
    {
        static A()
        {
            Console.WriteLine("static A()");
        }
        public static int a = methodA();
        static int methodA()
        {
            Console.WriteLine("A.methodA()");
            return 5;
        }
    }
}

这个的控制台输出是:

begin main()
initial B.b value 0
B.methodB()
A.methodA()
static A()
static B()
x = 5

这通过以下步骤证实了规范所说的内容:

  • 首先访问 B 类,然后开始变量 b 的静态字段初始化。
  • 在静态字段初始化完成之前,可以读取 B.b 的默认值(0,如 @Blindy 所说)。
  • 它遇到了一个对 A 类的调用,它还没有被初始化。
  • 它暂停 B 类的初始化并开始 A 类的初始化
  • 发生变量 a 的静态字段初始化,将值设置为 5(您还可以在静态字段初始化完成之前查看此属性的默认值 0,但我相信这可能发生的唯一位置是 methodAreturn之前)
  • 然后调用静态构造函数。
  • B 类简历的初始化
  • B 类中的静态构造函数被调用。
  • 最终,5 的值按期望/预期返回 B.b

这里似乎没有任何竞争条件的可能性,因为第一次引用具有静态成员的类时,当前发生的任何事情都会立即暂停,并且该类已完全初始化。

(至少在单线程代码中,这是 OP 所要求的。我最初的想法是多线程代码也不会引入竞争条件,但我不能肯定地说)

【讨论】:

  • 它们不是静态类(这是另一回事)。还有,问。据我了解,当 C# 初始化类时,它会尝试将其全部初始化(默认值、字段初始化、静态常量),并且仅在引用其他类时才停止?
  • 谢谢,你说得对,我的语言很草率,称它们为静态类,而不是具有静态成员的常规类。我已经编辑了我的答案。是的,这也是我的理解。我认为任何外部依赖都是一样的,无论是(在这种情况下)一个类的初始化,还是一个函数调用,或者其他什么......它将停止/暂停以处理该外部调用/初始化,然后在完成后继续。
【解决方案2】:

你是对的,这是语言规范本身的竞争条件。任何一个都将首先运行,您无法控制它。你要么祈祷链接你的代码的特定编译,以及运行你的代码的 JIT 让它“正确”(从你的角度来看),要么你只是编写正确的、确定性的代码。

摆脱这种情况的一种选择是使用一个静态构造函数按顺序初始化依赖的静态变量。

它从a获取垃圾值

顺便说一句,这永远不会发生,该字段将被零初始化或由您的代码初始化。

【讨论】:

  • 没有竞态,静态构造函数按顺序运行,参见15.12 of the ECMA-334 spec15.5.6.2。只有当有循环引用时才有比赛,这里没有,或者两个字段都引用...
  • ...另一种类型的公共字段,前两种类型都没有静态构造函数
  • 谁支持这些随机的 cmets?当类可以在不同的文件中并且它们链接在一起时,您将“按顺序”归因于什么可能的含义?什么顺序?您的订单?好在 C# 是一个读心器,Charlie face here 保证这段代码将始终按预期工作!
  • 当我说“按顺序”时,我并不是指文本顺序,它只与单个非partial 类相关。我的意思是,在示例中,B.b 不可能是 5 以外的任何值,因为它的初始化程序在第一次被引用时运行,进而引用和初始化 A.a。它根本不可能是其他任何东西,甚至不是 0,因为没有循环引用。
  • @Charlieface 感谢文档,第 334 页 (15.12) 有一个与我的非常相似的示例(尽管它是关于循环引用),但它准确地显示了在我的情况下会发生什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-21
  • 2010-10-09
  • 2013-08-26
  • 1970-01-01
  • 2016-10-12
  • 2012-04-23
  • 2021-06-04
相关资源
最近更新 更多