在C#中访问变量是原子操作吗?

我一直认为,如果多个线程可以访问一个变量,那么该变量的所有读取和写入操作都必须受到同步代码的保护,例如“ lock”语句,因为处理器可能会在中途切换到另一个线程写。


但是,我正在使用Reflector浏览System.Web.Security.Membership,发现了如下代码:


public static class Membership

{

    private static bool s_Initialized = false;

    private static object s_lock = new object();

    private static MembershipProvider s_Provider;


    public static MembershipProvider Provider

    {

        get

        {

            Initialize();

            return s_Provider;

        }

    }


    private static void Initialize()

    {

        if (s_Initialized)

            return;


        lock(s_lock)

        {

            if (s_Initialized)

                return;


            // Perform initialization...

            s_Initialized = true;

        }

    }

}

为什么在锁之外读取s_Initialized字段?另一个线程无法尝试同时写入吗?变量的读写是原子的吗?


阿波罗的战车
浏览 725回答 3
3回答

DIEA

对于确定的答案,请转到规格。:)CLI规范的第I部分,第12.6.6节指出:“符合规范的CLI必须保证对所有不超过本机字大小的正确对齐内存位置的读写访问是原子的,而对某个位置的所有写访问都相同”。这样就可以确定s_Initialized永远不会不稳定,并且对小于32位的原始类型的读写是原子的。特别是,double和long(Int64和UInt64)不能保证在32位平台上是原子的。您可以使用Interlocked类上的方法来保护这些方法。另外,虽然读写是原子的,但由于必须读取,操作和重写原始类型,因此存在一种竞争条件,具有加,减,递增和递减原始类型。互锁的类使您可以使用CompareExchange和Increment方法保护它们。互锁会形成内存屏障,以防止处理器对读取和写入进行重新排序。在此示例中,锁创建了唯一需要的屏障。

偶然的你

徘徊-标题中的问题绝对不是Rory提出的真正问题。名义问题的答案很简单:“否”,但是当您看到真正的问题时,这根本没有帮助。我认为没有人给出简单的答案。罗里问的真正问题要晚得多了,并且与他所举的例子更相关。为什么在锁之外读取s_Initialized字段?答案也很简单,尽管与变量访问的原子性完全无关。s_Initialized字段在锁外部读取,因为锁很昂贵。由于s_Initialized字段本质上是“一次写入”,因此它永远不会返回假肯定。在锁外阅读它是经济的。这是一种低成本的具有活性高的有效益的机会。这就是为什么要在锁之外读取它的原因-除非另有说明,否则避免支付使用锁的费用。如果锁便宜,那么代码会更简单,并省略该第一检查。(编辑:随后来自rory的回应很好。是的,布尔读取是非常原子的。如果有人使用非原子布尔读取构建了处理器,那么它们将在DailyWTF中使用。)
打开App,查看更多内容
随时随地看视频慕课网APP