C# 内存模型和非易失性变量在其他线程创建之前初始化

发布于 2024-09-16 07:27:26 字数 775 浏览 5 评论 0原文

我有一个与 C# 内存模型和线程相关的问题。我不确定以下代码在没有 volatile 关键字的情况下是否正确。

public class A {
  private int variableA = 0;

  public A() {

    variableA = 1;

    Thread B = new Thread(new ThreadStart(() => printA())).Start();
  }

  private void printA() {
    System.Console.WriteLine(variableA);
  }
}

我担心的是,是否可以保证线程 B 在不使用易失性的情况下看到值为 1 的变量 A?在主线程中,我只在构造函数中将 1 分配给变量 A。之后我不再接触变量A,它仅在线程B中使用,因此锁定可能不是必需的。

但是,是否可以保证主线程将刷新其缓存并将variableA内容写入主内存,以便第二个线程可以读取新分配的值?

另外,是否保证第二个线程将从主存中读取variableA的内容?是否可能发生一些编译器优化,线程 B 可以从缓存而不是主内存中读取变量 A 的内容?当指令的顺序改变时,可能会发生这种情况。

当然,将易失性添加到variableA声明将使代码正确。但是,有必要吗?我这样问是因为我在构造函数中编写了一些带有一些非易失性变量初始化的代码,并且这些变量稍后被一些计时器线程使用,我不确定它是否完全正确。

同样的代码在 Java 中怎么样?

谢谢,米哈尔

I have a question related to the C# memory model and threads. I am not sure if the following code is correct without the volatile keyword.

public class A {
  private int variableA = 0;

  public A() {

    variableA = 1;

    Thread B = new Thread(new ThreadStart(() => printA())).Start();
  }

  private void printA() {
    System.Console.WriteLine(variableA);
  }
}

My concern is if it is guaranteed that the Thread B will see variableA with value 1 without using volatile? In the main thread I am only assigning 1 to variableA in the constructor. After that I am not touching variableA, it is used only in the Thread B, so locking is probably not necessary.

But, is it guaranteed that the main thread will flush his cache and write the variableA contents to the main memory, so the second thread can read the newly assigned value?

Additionally, is it guaranteed that the second thread will read the variableA contents from the main memory? May some compiler optimizations occur and the Thread B can read the variableA contents from the cache instead of the main memory? It may happen when the order of the instructions is changed.

For sure, adding volatile to the variableA declaration will make the code correct. But, is it neccessary? I am asking because I wrote some code with some non volatile variables initialization in the constructor, and the variables are used later by some Timer threads, and I am not sure if it is totally correct.

What about the same code in Java?

Thanks, Michal

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

陪你到最终 2024-09-23 07:27:26

有很多地方会产生隐式记忆障碍。这是其中之一。启动线程会创建完整的屏障。因此,对 variableA 的写入将在线程启动之前提交,并且将从主内存获取第一次读取。当然,在 Microsoft 的 CLR 实现中,这有点没有实际意义,因为写入已经具有易失性语义。但 ECMA 规范中没有提供相同的保证,因此从理论上讲,Mono 实现在这方面可能会有不同的行为。

我关心的是是否能保证
线程 B 将看到变量 A
值1而不使用易失性?

在这种情况下……是的。但是,如果您继续在第二个线程中使用 variableA,则在第一次读取后无法保证它将看到更新。

但是,是否可以保证主要
线程将刷新他的缓存并写入
变量A的内容到main
内存,因此第二个线程可以读取
新分配的值?

是的。

此外,是否可以保证
第二个线程将读取
main 中的变量A 内容
内存?

是的,但仅限于第一次阅读时。

当然,向
变量声明将使
代码正确。但是,有必要吗?

在这个非常具体和狭窄的情况下......不。但是,一般来说,建议您在这些场景中使用 volatile 关键字。当场景变得更加复杂时,它不仅会使您的代码线程安全,而且还有助于记录该字段将被多个线程使用这一事实,并且您已经考虑了使用锁的含义 -自由策略。

There are a lot of places where implicit memory barriers are created. This is one of them. Starting threads create full barriers. So the write to variableA will get committed before the thread starts and the first reads will be acquired from main memory. Of course, in Microsoft's implementation of the CLR that is somewhat of a moot point because writes already have volatile semantics. But the same guarentee is not made in the ECMA specification so it is theorectically possible that the Mono implemenation could behave differently in this regard.

My concern is if it is guaranteed that
the Thread B will see variableA with
value 1 without using volatile?

In this case...yes. However, if you continue to use variableA in the second thread there is no guarentee after the first read that it will see updates.

But, is it guaranteed that the main
thread will flush his cache and write
the variableA contents to the main
memory, so the second thread can read
the newly assigned value?

Yes.

Additionally, is it guaranteed that
the second thread will read the
variableA contents from the main
memory?

Yes, but only on the first read.

For sure, adding volatile to the
variableA declaration will make the
code correct. But, is it neccessary?

In this very specific and narrow case...no. But, in general it is advised that you use the volatile keyword in these scenarios. Not only will it make your code thread-safe as the scenario gets more complicated, but it also helps to document the fact that the field is going to be used by more than one thread and that you have considered the implications of using a lock-free strategy.

薄荷港 2024-09-23 07:27:26

Java 中的相同代码绝对没问题——新线程的创建实际上充当了一种屏障。 (程序文本中早于线程创建的所有操作都“发生在”新线程启动之前。)

但是,我不知道 .NET 中对于新线程创建有何保证。更令人担忧的是使用 Control.BeginInvoke 等时可能会出现延迟读取......我还没有看到针对这些情况的内存屏障的任何保证。

说实话,我怀疑没问题。我怀疑任何需要在像这样的线程之间进行协调的事情(无论是创建一个新线程还是编组对现有线程的调用)都将在所涉及的两个线程上使用完整的内存屏障。然而,你的担心是绝对正确的,我希望你能从比我更聪明的人那里得到更明确的答案。您可能想给 Joe Duffy 发电子邮件以了解他对此的看法......

The same code in Java is definitely okay - the creation of a new thread acts as a sort of barrier, effectively. (All actions earlier in the program text than the thread creation "happen before" the new thread starts.)

I don't know what's guaranteed in .NET with respect to new thread creation, however. Even more worrying is the possibility of a delayed read when using Control.BeginInvoke and the like... I haven't seen any guarantees around memory barriers for those situations.

To be honest, I suspect it's fine. I suspect that anything which needs to coordinate between threads like this (either creating a new one or marshalling a call onto an existing one) will use a full memory barrier on both of the threads involved. However, you're absolutely right to be concerned, and I'm hoping that you'll get a more definitive answer from someone smarter than me. You might want to email Joe Duffy to get his point of view on this...

向日葵 2024-09-23 07:27:26

但是,是否保证主线程会刷新他的缓存并将variableA内容写入主内存,

是的,这是由MS CLR内存模型保证的。对于 CLI 的其他实现则不一定如此(即,我不确定 Mono)。 ECMA 标准不要求它。

所以第二个线程可以读取新分配的值?

这需要刷新缓存。它可能是通过线程的创建来保证的(就像 Jon Skeet 所说)。然而,前一点并不能保证这一点。缓存会在每次写入时刷新,但不会在每次读取时刷新。

您可以使用 VolatileRead(ref variableA) 来确保这一点,但建议 (Jeffrey Richter) 使用 Interlocked 类。请注意,VolatileWrite() 在 MS.NET 中是多余的。

But, is it guaranteed that the main thread will flush his cache and write the variableA contents to the main memory,

Yes, this is guaranteed by the MS CLR memory model. Not necessarily so for other implementations of the CLI (ie, I'm not sure about Mono). The ECMA standard does not require it.

so the second thread can read the newly assigned value?

That requires that the cache has been refreshed. It is probably guaranteed by the creation of the Thread (like Jon Skeet said). It is however not guaranteed by the previous point. The cache is flushed on each write but not on each read.

You could make very sure by using VolatileRead(ref variableA) but it is recommended (Jeffrey Richter) to use the Interlocked class. Note that VolatileWrite() is superfluous in MS.NET.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文