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

Can inline substitution cause an infinite loop in multithreaded code?

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

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

这也引出了下一个问题。 getters 和 setters 在 classes 中呢? class 内定义的成员函数隐含地 inline,因此我认为可能会发生内联替换,因此存在同样的问题。 这是正确的吗?

done 的访问必须并行 保护 并且在线程之间 同步 。否则,处理器或编译器可能会 produce/execute 错误的指令序列。您当前的程序格式错误。

您面临的问题是 done 可以缓存在 L1 CPU 缓存中(取决于处理器)或者编译器可以优化对 done 全局变量的访问(通常通过将其放入寄存器中,尽管许多编译器实际上并不这样做)。

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

有关更多信息,您可以查看缓存一致性协议,例如 MOESI and look

在这种情况下,主要是由于 staticinline 关键字,主流编译器(如 GCC 和 Clang)实际上 optimize the code to no-op instruction(这在 C++ 标准方面是完全合法的)帮助编译器。 std::atomic.

不是这种情况

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

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

当然,编译器中存在很大的错误空间,并且很可能存在这样的错误。实际上,内联编译器会检测内联可能导致问题的情况,然后避免内联或修复问题。在大多数情况下,内联是安全的,你不内联的原因是内联什么代码不明确(如果有 overloading/inheritance/virtual 调用正在进行),递归(你不能将函数内联到自身无限,但你可以达到一个限制)或性能考虑(通常会增加代码大小,但也会导致代码块变得如此之大以至于其他优化被禁用)。