易变量

时间:2016-12-07 20:07:36

标签: c# c volatile

我最近接受了一家软件公司的采访,他问我以下问题:

  

您能告诉我在变量前面添加 volatile 的内容吗?你能解释一下为什么它很重要吗?

我的大多数编程知识都来自C,但是工作职位是针对C#(我想我可能会根据具体问题的需要添加这些信息)

我回答说它只是让 编译器 知道变量可以跨进程或线程使用,并且它不应该对该变量使用优化;优化它可以恶化行为。简而言之,它是对编译器的警告。

然而,根据采访者的说法,反过来说,volatile关键字警告 OS ,而不是编译器。

我对此感到有些困惑,所以我做了一些研究,实际上找到了相互矛盾的答案!一些消息来源称其为编译器,而其他人则称为操作系统。

这是什么?是否因语言而异?

2 个答案:

答案 0 :(得分:23)

  

我回答说它只是让编译器知道变量可以跨进程或线程使用,并且它不应该对该变量使用优化;优化它可以恶化行为。简而言之,这是对编译器的警告。

这对C#来说是正确的方向,但是错过了一些重要的方面。

首先,完全删除“进程”。变量不在C#中的进程之间共享。

其次,不要专注于优化。而是专注于允许的语义。编译器不需要生成最佳代码;需要编译器来生成符合规范的代码。重新排序不一定是出于性能原因而需要更快/更小/无论如何。 volatile声明对多线程程序的允许语义添加了额外的限制。

第三,不要将其视为对编译器的警告。它是编译器的一个指令:生成保证符合volatile变量规范的代码。编译器如何做到这一点取决于它。

问题的实际答案

  

你能告诉我在变量前加上volatile的含义吗?

是:C#编译器和运行时环境具有很大的自由度,可以根据他们认为合适的任何原因重新排序变量读取和写入。它们仅限于那些在单个线程上保留程序含义的重新排序。所以“x =​​ y; a = b;”可以将读取的b移到读取之前;这是合法的,因为结果没有变化。 (这不是重新排序的唯一限制,但在某种意义上它是最基本的限制。)但是,重新排序允许在多个线程上注意到;另一个线程可能会观察到在y之前读取b。这可能会导致问题。

C#编译器和运行时对可以如何相对于彼此重新排序易失性读取和写入有额外的限制,以及如何针对其他事件(例如线程启动和停止,锁定,异常)对它们进行排序被抛出,等等。

有关读取,写入和其他效果的观察顺序的限制的详细列表,请参阅C#规范。

特别要注意,即使使用volatile变量,也不需要从所有线程看到所有变量访问的一致的总排序。具体而言,挥发性“读取变量的最新值”这一概念简直就是假的;这句话表明存在“最新价值”这一事实,这意味着完全一致的排序。

如果这听起来令人困惑,那就是。 不要编写跨线程共享数据的多线程程序。如果必须,请使用最高级别的抽象。几乎没有人应该编写使用volatile的代码;使用TPL并让它管理你的线程。

现在让我们在C语境中考虑你的答案。

关于C,问题是不适定的。在C#中,volatile是成员变量声明的修饰符;在C中,它是类型的一部分。所以说“变量之前”是模棱两可的;变量之前的位置? volatile int * xint * volatile x之间存在差异。 (你能看出区别吗?)

但更重要的是:C规范并不保证volatile对线程有任何特定的行为。如果您的C编译器这样做,那么这是编译器供应商对该语言的扩展。 C中的易失性保证在内存映射IO,长跳转和信号方面具有某些特性,这就是全部;如果你依赖它来获得关于线程的某些行为,那么你正在编写非可移植代码。

  

根据采访者的说法:反过来说,volatile关键字会警告操作系统,而不是编译器。

从开始到结束都是胡说八道。面试官不应该问他们不理解答案的问题。

答案 1 :(得分:3)

说实话,面试官提出的问题实际上有点模糊。

这实际上取决于他/她的意思" OS"。他们是在谈论"前期操作系统",纯软件方面的事情,还是他们可能误解了操作系统"作为硬件 - 软件关系,即RTE和MMM(我在我自己的一些个人访谈中看到了假设和比较)。我认为值得注意的是,这两者截然不同!如果他/她在谈论前者,那么 volatile不会"通知"操作系统。如果他们在谈论后者,那么(这是一个松散的是)。此时,您处于语言之间差异的范畴。正如Cody Gray所提到的,C#是一种托管语言,因此操作系统的后一种定义确实会得到通知"变量和采取的预防措施。

但是,无论在任何情况下或OS的定义,编译器 都会专门管理和处理volatile字段,而不管语言如何。否则,为什么首先要使用关键字?

在我个人看来,无论如何,我认为你的回答是正确的,尽管从评论来看,可能会变得复杂和忙乱。