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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9666/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-07-31 17:26:03  来源:igfitidea点击:

Is accessing a variable in C# an atomic operation?

提问by Rory MacLeod

I've been raised to believe that if multiple threads can access a variable, then all reads from and writes to that variable must be protected by synchronization code, such as a "lock" statement, because the processor might switch to another thread halfway through a write.

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

However, I was looking through System.Web.Security.Membership using Reflector and found code like this:

但是,我正在使用 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;
        }
    }
}

Why is the s_Initialized field read outside of the lock? Couldn't another thread be trying to write to it at the same time? Are reads and writes of variables atomic?

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

采纳答案by John Richardson

For the definitive answer go to the spec. :)

有关明确的答案,请参阅规范。:)

Partition I, Section 12.6.6 of the CLI spec states: "A conforming CLI shall guarantee that read and write access to properly aligned memory locations no larger than the native word size is atomic when all the write accesses to a location are the same size."

CLI 规范的第 I 部分第 12.6.6 节规定:“当对一个位置的所有写访问大小相同时,符合标准的 CLI 应保证对不大于本机字大小的正确对齐的内存位置的读写访问是原子的.”

So that confirms that s_Initialized will never be unstable, and that read and writes to primitve types smaller than 32 bits are atomic.

这证实了 s_Initialized 永远不会不稳定,并且读取和写入小于 32 位的原始类型是原子的。

In particular, doubleand long(Int64and UInt64) are notguaranteed to be atomic on a 32-bit platform. You can use the methods on the Interlockedclass to protect these.

特别是,doublelongInt64UInt64不能保证在 32 位平台上是原子的。您可以使用Interlocked类上的方法来保护这些。

Additionally, while reads and writes are atomic, there is a race condition with addition, subtraction, and incrementing and decrementing primitive types, since they must be read, operated on, and rewritten. The interlocked class allows you to protect these using the CompareExchangeand Incrementmethods.

此外,虽然读取和写入是原子的,但存在加法、减法、自增和自减原始类型的竞争条件,因为它们必须被读取、操作和重写。互锁类允许您使用CompareExchangeIncrement方法保护这些。

Interlocking creates a memory barrier to prevent the processor from reordering reads and writes. The lock creates the only required barrier in this example.

互锁会创建一个内存屏障,以防止处理器重新排序读取和写入。在这个例子中,锁创建了唯一需要的屏障。

回答by Keith

I thought they were - I'm not sure of the point of the lock in your example unless you're also doing something to s_Provider at the same time - then the lock would ensure that these calls happened together.

我认为它们是 - 我不确定你的例子中锁定的重点,除非你同时对 s_Provider 做一些事情 - 然后锁定将确保这些调用一起发生。

Does that //Perform initializationcomment cover creating s_Provider? For instance

//Perform initialization评论是否涵盖创建 s_Provider?例如

private static void Initialize()
{
    if (s_Initialized)
        return;

    lock(s_lock)
    {
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}

Otherwise that static property-get's just going to return null anyway.

否则,静态属性获取无论如何都会返回 null。

回答by John Richardson

The Initialize function is faulty. It should look more like this:

初始化功能有问题。它应该看起来更像这样:

private static void Initialize()
{
    if(s_initialized)
        return;

    lock(s_lock)
    {
        if(s_Initialized)
            return;
        s_Initialized = true;
    }
}

Without the second check inside the lock it's possible the initialisation code will be executed twice. So the first check is for performance to save you taking a lock unnecessarily, and the second check is for the case where a thread is executing the initialisation code but hasn't yet set the s_Initializedflag and so a second thread would pass the first check and be waiting at the lock.

如果没有在锁内进行第二次检查,初始化代码可能会被执行两次。因此,第一次检查是为了性能以节省您不必要的锁定,第二次检查是针对线程正在执行初始化代码但尚未设置s_Initialized标志的情况,因此第二个线程将通过第一次检查和在锁旁等待。

回答by olliej

What you're asking is whether accessing a field in a method multiple times atomic -- to which the answer is no.

您要问的是,是否多次原子地访问方法中的字段 - 答案是否定的。

In the example above, the initialise routine is faulty as it may result in multiple initialization. You would need to check the s_Initializedflag inside the lock as well as outside, to prevent a race condition in which multiple threads read the s_Initializedflag before any of them actually does the initialisation code. E.g.,

在上面的例子中,初始化例程是错误的,因为它可能导致多次初始化。您需要检查s_Initialized锁内部和外部的标志,以防止出现多个线程在其中s_Initialized任何一个实际执行初始化代码之前读取标志的竞争条件。例如,

private static void Initialize()
{
    if (s_Initialized)
        return;

    lock(s_lock)
    {
        if (s_Initialized)
            return;
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}

回答by Peteter

Perhaps Interlockedgives a clue. And otherwise this onei pretty good.

也许互锁提供了一个线索。否则这个我很不错。

I would have guessed that their not atomic.

我会猜到它们不是原子的。

回答by John Richardson

I think you're asking if s_Initializedcould be in an unstable state when read outside the lock. The short answer is no. A simple assignment/read will boil down to a single assembly instruction which is atomic on every processor I can think of.

我认为您是在问s_Initialized在锁外读取时是否可能处于不稳定状态。最简洁的答案是不。一个简单的赋值/读取将归结为单个汇编指令,它在我能想到的每个处理器上都是原子的。

I'm not sure what the case is for assignment to 64 bit variables, it depends on the processor, I would assume that it is not atomic but it probably is on modern 32 bit processors and certainly on all 64 bit processors. Assignment of complex value types will not be atomic.

我不确定分配给 64 位变量的情况是什么,它取决于处理器,我认为它不是原子的,但它可能在现代 32 位处理器上,当然在所有 64 位处理器上。复杂值类型的赋值不是原子的。

回答by OJ.

Reads and writes of variables are not atomic. You need to use Synchronisation APIs to emulate atomic reads/writes.

变量的读取和写入不是原子的。您需要使用同步 API 来模拟原子读/写。

For an awesome reference on this and many more issues to do with concurrency, make sure you grab a copy of Joe Duffy's latest spectacle. It's a ripper!

有关这方面的出色参考以及与并发性有关的更多问题,请确保获取 Joe Duffy 的最新奇观的副本。这是一个开膛手!

回答by Leon Bambrick

"Is accessing a variable in C# an atomic operation?"

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

Nope. And it's not a C# thing, nor is it even a .net thing, it's a processor thing.

不。它不是 C# 的东西,甚至也不是 .net 的东西,它是处理器的东西。

OJ is spot on that Joe Duffy is the guy to go to for this kind of info. ANd "interlocked" is a great search term to use if you're wanting to know more.

OJ 认为 Joe Duffy 是获取此类信息的最佳人选。如果您想了解更多信息,“互锁”是一个很好的搜索词。

"Torn reads" can occur on any value whose fields add up to more than the size of a pointer.

“撕裂读取”可以发生在任何字段加起来超过指针大小的值上。

回答by JoshL

Ack, nevermind... as pointed out, this is indeed incorrect. It doesn't prevent a second thread from entering the "initialize" code section. Bah.

Ack,没关系......正如所指出的,这确实是不正确的。它不会阻止第二个线程进入“初始化”代码部分。呸。

You could also decorate s_Initialized with the volatile keyword and forego the use of lock entirely.

您还可以使用 volatile 关键字装饰 s_Initialized 并完全放弃使用锁。

回答by John Richardson

You could also decorate s_Initialized with the volatile keyword and forego the use of lock entirely.

您还可以使用 volatile 关键字装饰 s_Initialized 并完全放弃使用锁。

That is not correct. You will still encounter the problem of a second thread passing the check before the first thread has had a chance to to set the flag which will result in multiple executions of the initialisation code.

那是不正确的。在第一个线程有机会设置标志之前,您仍然会遇到第二个线程通过检查的问题,这将导致多次执行初始化代码。