内联替换会在多线程代码中导致无限循环吗



请注意:这只是一个出于好奇的问题,而不是关于编写更好的多线程代码。当然,我不会也不会在实际项目中编写这样的代码

添加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) {
}
}

这也引出了我的下一个问题。类中的gettersetters怎么样?类内部定义的成员函数是隐式inline,所以我认为可能会发生内联替换,因此也会出现同样的问题。这是正确的吗?

done的访问必须并行保护,并且在线程之间同步。否则,处理器或编译器可能产生/执行不正确的指令序列。您当前的程序格式不正确。

您面临的问题是,done可以缓存在一级CPU缓存中(取决于处理器(,或者编译器可以优化对done全局变量的访问(通常通过将其放入寄存器,尽管许多编译器在实践中没有(。

您需要使用原子指令互斥/锁(或任何同步机制(,以便done在修改时可以被其他线程看到。使用原子指令,编译器生成适当的内存围栏,不将done放入寄存器或/并生成同步通信总线的指令(例如,在x86_64上(。

有关更多信息,您可以查看像MOESI这样的缓存一致性协议,以及x86如何处理原子指令。

在这种情况下,主流编译器(如GCC和Clang(实际上将代码优化为无操作指令(这在C++标准中是完全合法的(,主要是因为staticinline关键字有助于编译器。std::atomic的情况并非如此。

在您提供的情况下,除了在没有某种锁的情况下访问数据的正常问题之外,内联没有提供任何额外的问题。这并不是说将getDone()内联到done会以某种方式将done的属性擦除为非静态或其他什么。就像你在示例代码中直接编码done一样,存在无限循环的风险,但无论是否内联,这都是正确的,因为cpp的规范将这种对变量的无保护多线程访问保留为(我相信(未定义的行为,所以你可能有一个无限循环,如果另一个线程更新done,你可能会有一个最终停止的循环,如果另一个线程像受到保护一样更新done,那么您可能会有一个~立即停止的循环。奇怪的事情发生在未定义的行为中,所以没有简单的方法来回答你的问题(AFAIK,我还没有就这一点对C++11规范进行彻底的调查(。

正确实现的内联不会改变以不安全的方式执行的内容,因为这是正确内联的定义。如果内联一个方法改变了它的行为,使它停止在多线程环境中工作,那么编译器中就会出现一个需要修复的错误。

当然,编译器中有很大的错误空间,这样的错误很可能存在。在实践中,内联编译器检测内联可能导致问题的情况,并避免内联或修复问题。在大多数情况下,内联是安全的,你不内联的原因是因为内联的代码不明确(以防发生重载/继承/虚拟调用(、递归(你不能无限地将函数内联到它自己中,但你可以达到极限(,或性能考虑(通常增加代码大小,但也会导致代码块变得太大,从而禁用其他优化(。

最新更新