xiewajueji's blog

This is a site that express xiewajueji's views on software engineering and entertainment things.

about
26 May 2021

[翻译] volatile关键字应该有原子性和线程可见性语义吗?

by xiewajueji

一篇关于volatile的文章,说明volatile在c/c++应该在什么情况下使用.


通常来说,C和C++的volatile关键字的语义并不清晰.特别的,这些语言说明这个操作是“根据不同的虚拟机的规则有所不同”(C99,6.7.3-6) 但是,至少在pthread的环境中,它通常不会被解释实施线程间的可见性.David Butenhof说,“volatile的使用没有做任何事,只是阻止编译器做有用的和期望的优化,没有给代码提供任何所谓的’线程安全’的保障”(comp.programming.threads.posting,July 3,1997,according to the Google archive).

作为结果,大部分的实现不会插入有效的内存屏障来保证其他的线程,或者甚至是硬件设备,按照它们发布的顺序看到易失性操作.

在有些平台上,提供了一些有限的顺序保证,因为它们是自动被下层的硬件强制的或者使用奔腾CPU,因为对于易变引用,不同的指令被生成.但是特定的规则是高度依赖于平台的.即使它们被特定的平台特殊规定,它们也有可能有不一致的实现.

并不清楚怎么在可以移植的代码中使用如此弱的保证,除非在一些我们列出来的很稀有的场合.

这就提出了一个问题,volatile是否应该被赋予同时提供原子性和线程间的可见性的意义,大致和Java的VolatileS一致.

尽管我们抽象地认为这可以为当前没有可移植语义的东西提供更多语义来提供实质性改进,但是似乎存在许多向后兼容性问题驱动的实际障碍,这些障碍至少让我们犹豫.这些将在下面讨论.

现存的可移植用法

现在存在三种volatile的用法能够真正的可移植.尽管这些用法出现的相对少,但是,如果我们采用了更强的语义,它们会一些平台上慢下来.

这些情况中,最后一个情况出现的更频繁,但是不太可能对性能至关重要.

现存的不可移植用法

我们相信volatile被广泛使用,例如在操作系统内核中和显示的平台相关的内存屏障指令一起使用.尽管这些用法本质上是不可移植的并且不是严格准守标准编制,它们代表了一个大量的代码库改变volatile的意义将会带来不可逆的影响.使用特殊的编译器选项来编译内核是很正常的,但是避免人们对应用选择新旧语义是可取的.还不清楚这是不是个严重的问题.

增加原子性的问题

在线程之间通信也有作用的volatile变量,更新必须是原子的,读这些变量也是.基于锁的模拟可能不实用,因为它不能和异步信号一起工作.但是我们通常能给一些类型提供硬件级别的原子性.这就提出了一个问题,那就是哪些能做哪些不能做.

提供诊断是有问题的,因为在现代的代码中通常将现有代码整个都声明为易失性的,无需原子更新.仅在使用中才提供诊断可能是可以接受的.在结构体的分配时,将需要无法实现的原子性.但是即使那样,也可能存在对已经并且仍然正确的现有代码引入诊断的危险.

如果在一个volatile操作不是原子时没有发出诊断,那么我们有很大的可能性引入不明显的,不可重现的错误.更复杂的是,只有语言定义了哪些类型可以与volatile一起使用时,这才真正合理.这是很难做到的,因为C++支持一些小端嵌入式平台,因为它操出了自然的字长,因此可能难以提供指针的原子更新.

结论

如果我们从头开始设计一门语言,我们相信提供volatile语义来允许此类变量用于线程间通信显然是可取的.但是,现在进行这样的更改将增加一些正确的现有代码的性能成本.并且现有代码主题将很难或者不可能为此类线程通信用途正确发出诊断信息.

由于”原子”对象的库接口可以支持相同的用途,因此尚不清楚这种更改是否值得过渡成本.如果以后有证据表明更改的好处将胜过缺点,那么为volatile提供线程语义就不会有困难.该更改将具有清理当前未定义构造的语义的好处.

tags: c/c++