内联替换会导致多线程代码中的无限循环吗?
请注意:这只是出于好奇而提出的问题,与编写更好的多线程代码无关。当然,我不会也不会在实际项目中编写这样的代码。
添加关键字时可能会发生内联替换inline。所以我很好奇。
假设我们有这样的代码:
static bool done = false;
inline void setDone(bool newState) {
done = newState;
}
inline bool getDone() {
return done;
}
void someWorkerThreadJob() {
// Accessing without any lock/atomic/volatile
while (getDone() == false) {
}
}
可以someWorkerThreadJob()像下面这样编译并进入无限循环吗?
void someThreadJob() {
while (done == false) {
}
}
这也将我引向了下一个问题。类中的getter和setter怎么样?在类中定义的成员函数是隐式的inline,所以我认为可能会发生内联替换,因此同样的问题。这样对吗?
回答
done必须在线程之间并行和同步地保护对的访问。否则,处理器或编译器会产生/执行不正确的指令序列。您当前的程序格式不正确。
您面临的问题是done可以缓存在 L1 CPU 缓存中(取决于处理器),或者编译器可以优化对done全局变量的访问(通常通过将其放在寄存器中,尽管许多编译器实际上并没有这样做)。
您需要使用原子指令或互斥锁/锁(或任何同步机制),以便done在修改时可以被其他线程看到。使用原子指令,编译器生成适当的内存栅栏,不放入done寄存器或/和生成同步通信总线的指令(例如在 x86_64 上)。
欲了解更多信息,你可以给看看缓存一致性协议,如MOESI,看看如何与原子能指令的x86交易。
在这种情况下,主流编译器(如 GCC 和 Clang)实际上将代码优化为无操作指令(这在 C++ 标准中是完全合法的),主要是由于static和inline关键字帮助编译器。情况并非如此std::atomic。