废话文学上一次这么流行的时候,还是在上一次流行废话文学的时候。
抖音上有个相声演员,每天的更新就是各种片汤话和废话,絮絮叨叨一大堆,一句有用的信息都没有。评论区都是调侃:“哎吓死我了,他差点就把正事说出来了”,“有领导开会那味儿了”。
还有一个最近爆火的叫陈依涵的小姐姐,每天更新一款无用的软件,诸如输入自己身高就能得出自己身高的计算器、百进制的分秒转换器等,也是废话文学的另一种表现形式。

与热衷于废话文学,被各种无厘头戳中笑点的我们相比,编译器就像个严肃高冷,追求效率,没有任何生活情趣的老头子一样,极其讨厌废话文学。
考虑这样一种情况:
- x = 1;
- x = 0;
- x = 1;
- x = 0;
- x = 1;
编译器一看,好家伙,这不纯纯的废话嘛!
于是编译器优化时很有可能会将上述五行代码优化为一行:
x = 1;
因为它觉得你这个人的水平太挫,说话也啰嗦,它要帮帮你。
可是,我这个x是表示板载LED的地址啊,我这么干,其实是想让这个LED闪烁...

这种编译器优化带来的问题,即使是原子量atomic也无能为力。此时我们从武器库里找到了另一把武器:volatile。
volatile
volatile会告知编译器,对这种情况不要进行优化,保留这些看似冗余加载和废弃存储的无效操作,虽然是废话,但是我爱听。
这几乎是volatile最适用的场景了,其他场景我觉得volatile都不算是最优解。
我们来回顾一下一些教材上对C/C++里volatile关键字的解释:
volatile提醒编译器它后面所修饰的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
翻译翻译就是volatile会保证本线程对变量做的修改,都会立即被其他线程感知到。说官方点就是对其他线程可见,说人话就是其他线程拿到的值都是最新的,是修改生效的。
看了上篇文章的读者,一定会想到内存序。这不就是内存序的功能嘛?
而且内存序还要比它强大的多,因为volatile只能保证被volatile修饰的变量对其他线程可见,而原子量+内存序不仅可以保证对原子变量的修改对其他线程可见,还可以确保内存里其他变量的修改都对所有线程可见!
atomic
以上一篇文章《如何帮罗小猪的时间管理进一步提高性能?无锁并发!》提到过的这样一种情况为例:
// 计算值
data = calculate();
//设置flag,通知其他任务值已可用
flag = true;
编译器优化很有可能会把flag=true这句放到calculate计算值之前!因为它觉得两者似乎是不同的内存不同的寄存器。
编译器说我估计calculate函数还要花费一点时间才能执行完,索性就先把下面的flag=true给执行了。反正内存地址不是同一个,不冲突。
但这却有可能会让其他监听flag状态的线程提前执行,完全违背了我们的设计初衷。
这时如果我们将flag用原子量atomic来定义,用store(memory_order_release)来赋值,则可以保证上述代码的正常执行次序,防止被其他线程在flag未设置之前拿到计算值。

但这种情况,volatile是无能为力的。假如用volatile来修饰flag的话,它最多保证flag的变化能立刻被另一线程感知到,但却无法保证另一个线程拿到的data是被calculate过的。即C/C++里的volatile对于指令重排序无法进行限制。
况且,volatile还有个致命的问题是,它并不能保证变量操作的原子性。也就是说,虽然你对其他线程可见,但你的值并不一定符合预期。
比如用volatile修饰一个变量value:
volatile int value = 0;
value++;
value--;
std::cout<<value;
如果有一个线程正在执行这段代码,另一个线程去读取value的值的话,那这个值会出现无数种可能,甚至可能是负数或者一个特别大的数。
因为某个线程刚刚在value这块内存上写了一半,就被另一个线程读取,那么它读到的这个值属于未定义行为,没有任何意义。
基于以上两点,即:
1.volatile不能保证原子操作。
2.volatile不能限制指令重排序。
所以并发多任务的情况下用volatile可能并不是一个好的选择。

总结一下,像状态寄存器、映射到内存地址上的 I/O 操作、涉及硬件操作的变量需要加volatile,因为对它们的每一次操作都有其意义,并不是废话文学。
而并发时,多线程多任务环境下各任务间共享的标志,其实更应该用原子量和内存序,或者直接加互斥锁,以确保共享区操作的原子性和顺序性。
所以其实volatile和atomic是应用于不同场景的,甚至可以叠加使用。比如:
volatile std::atomic<int> value;
这个式子表示对value的操作都是原子性的,并且它的废话文学也不可以被优化掉。
后 记
最近发的文章关于C/C++语言方面的比较多,事实上我是打算先从语言基础开始,后续关于编译器、单片机、RTOS、Linux应用、内核、驱动等嵌入式相关的技术知识和经验技巧,以及博主从事过的物联网、半导体行业的实战经验和职场分享,都会陆续在这个号上发布。
还请关注。
上次用还请关注这句话结尾的时候,还是在上次文章结尾的时候。