C++ 优化会破坏线程安全吗?
我不熟悉原子操作,所以我可能会弄错一些概念 考虑到上述代码,假设加载和存储C++ 优化会破坏线程安全吗?,c++,multithreading,atomic,C++,Multithreading,Atomic,我不熟悉原子操作,所以我可能会弄错一些概念 考虑到上述代码,假设加载和存储int类型是原子的[注1],那么getBar()只能在fetch()之前或之后获取bar 但是,如果编译器足够聪明,它可以优化temp,并将其更改为: class Foo{ public: void fetch(void) { int temp=-1; someSlowFunction(&temp); bar=temp; } int g
int
类型是原子的[注1],那么getBar()
只能在fetch()之前或之后获取bar
但是,如果编译器足够聪明,它可以优化temp
,并将其更改为:
class Foo{
public:
void fetch(void)
{
int temp=-1;
someSlowFunction(&temp);
bar=temp;
}
int getBar(void)
{
return bar;
}
void someSlowFunction(int *ptr)
{
usleep(10000);
*ptr=0;
}
private:
int bar;
};
在这种情况下,getBar()
可以在特定的定时条件下获得-1
或someSlowFunction()中的其他中间状态
这有可能吗?标准是否阻止了此类优化
注1:
在这方面,语言标准没有提到原子性
案例也许整数赋值是原子的,也许不是。自从
非原子操作不作任何保证,纯整数
根据定义,C语言中的赋值是非原子的。
实际上,我们对目标平台的了解通常比
那个例如,众所周知,在所有现代x86、x64、,
安腾、SPARC、ARM和PowerPC处理器,纯32位整数
只要目标变量是自然变量,赋值就是原子的
对齐。您可以通过查阅处理器手册和/或
编译器文档。在游戏行业,我可以告诉你
很多32位整数赋值都依赖于这种特殊的保证
我把ARCORTEX-A8放在这里,所以我认为这是一个安全的假设。
由于中间函数调用,它可能不会优化这种方式,但是可以定义<代码> TEMP作为<代码> Value,这将告诉编译器不执行这些类型的优化。
根据平台的不同,您肯定会遇到多字节数量处于不一致状态的情况。它甚至不需要与线程相关。例如,在电源断电期间遇到低电压的设备可能会使内存处于不一致的状态。如果指针被破坏,那通常是坏消息
我在一个没有互斥锁的系统上实现这一点的一种方法是确保每一条数据都能被验证。例如,对于每个数据T,将有一个验证校验和C和一个备份U
set
操作如下:
void Foo::fetch(void)
{
bar=-1;
someSlowFunction(&bar);
}
U = T
T = new value
C = checksum(T)
而get
操作如下所示:
void Foo::fetch(void)
{
bar=-1;
someSlowFunction(&bar);
}
U = T
T = new value
C = checksum(T)
这保证了返回的内容处于一致状态。我会将此算法应用于整个操作系统,例如,可以恢复整个文件
如果您想确保原子性而不进入复杂的互斥体,那么请尝试使用尽可能最小的类型。例如,bar
是否需要是int
或unsigned char
或bool
就足够了?编译器优化不能破坏线程安全
然而,您可能会遇到代码优化方面的问题,这些代码看起来是线程安全的,但实际上只是因为运气好才起作用
如果从多个线程访问数据,则必须
- 使用
std::mutex
或类似方法保护适当的部分
- 或者,使用
std::atomic
否则,编译器可能会进行几乎不可能预期的优化
我建议大家观看,在评论中回答问题后,这会更有意义。假设fetch()和getBar()是从不同的线程调用的,那么让我们在这里分析线程安全性。需要考虑以下几点:
- “脏读”,或由于写入中断而导致的garabage读取。虽然这是一种普遍的可能性,但在我熟悉的3个芯片系列中,对齐INT不会发生这种情况。现在让我们放弃这种可能性,假设读取值是alwats clean
- “不正确的阅读”,或者从酒吧里阅读一些从未写过的东西。可能吗?在我看来,在编译器部分优化temp是可能的,但我不是这方面的专家。让我们假设它不会发生。警告仍然存在——你可能永远看不到bar的新价值。不是在合理的时间内,只是永远不会
编译器可以应用导致相同可观察行为的任何转换。对局部非易失变量的赋值不是可观察行为的一部分。编译器可能决定完全消除temp
,直接使用bar。它还可能决定bar将始终以值0结束,并在函数的开头设置(至少在您的简化示例中)
然而,情况更为复杂,因为现代硬件也优化了执行的代码。这意味着CPU会对指令重新排序,如果不使用特殊指令,程序员或编译器都不会对指令重新排序产生影响。您需要一个std::atomic
,明确地限制内存(我不建议这样做,因为这很棘手),或者使用一个互斥锁,它也可以充当内存限制。如果需要同步化,那么使用或“假设加载和存储int类型是原子的”,什么时候是真的?”然后getBar()
只能在fetch()之前或之后获取条形图。
如果编译器足够聪明,它可以优化temp并将其更改为:“也不是真的。此代码是线程安全的-那里没有线程,没有同步,什么都没有。为了得到一个真正的答案,我建议您在这里详细说明线程的用法。使用最小的类型如何帮助线程安全?同样,volatile也不能保证任何线程安全。@NathanOliver-任何比体系结构的数据总线宽度更宽的数据类型都容易导致写入操作中断,从而导致读取操作在不一致的状态下获取值。使用volatile
是为了解决任何编译器优化问题