符合规范的实现是否可以对这个示例进行改写,以消除加载操作?

编程语言 2026-07-08

请考虑下面这个例子:

#include <atomic>
#include <thread>

int main(){
  std::atomic<int> ack = 1;
  std::atomic<int> val = 0;
  std::jthread t1([&]{
    while(ack.load(std::memory_order::relaxed) !=0); // #0
    val.store(1,std::memory_order::release); // #1
  });
  std::jthread t2([&]{
    val.load(std::memory_order::acquire); // #2
    ack.fetch_sub(1,std::memory_order::relaxed); // #3
    val.load(std::memory_order::acquire); // #4
  });
}

第一个问题是,在这个例子中,是否不存在一个可能的执行使得 #2 读取 #1,对吗?在那个可能的执行中,#0 将在 #3 之前发生,使得循环永远不会退出。

接着,考虑这个变体示例

#include <atomic>
#include <thread>
#include <chrono>

using namespace std::chrono_literals;

int main(){
  std::atomic<int> ack = 1;
  std::atomic<int> val = 0;
  std::jthread t1([&]{
    int count = 0;
    while(ack.load(std::memory_order::relaxed) !=0){ // #0
       if(++count == 5){
         break;
       }
       std::this_thread::sleep_for(1000ms);
    }
    val.store(1,std::memory_order::release); // #1
  });
  std::jthread t2([&]{
    val.load(std::memory_order::acquire); // #2
    ack.fetch_sub(1,std::memory_order::relaxed); // #3
    val.load(std::memory_order::acquire); // #4
  });
}

在这个例子中,#2 读取 #1 是一个有效的可能执行,因为循环可以因为 break 而退出。

第二个问题是,符合规范的实现是否允许将 t1 中的整段循环改写为如下所述的形式?

while(count < 4){
  ++count;
  std::this_thread::sleep_for(1000ms);
}

论证来自 [intro.abstract] 第6页

符合规范的实现执行一个格式正确的程序时,应产生在同一程序和同一输入下,抽象机器的相应实例的定义前缀的某个可能执行之一所对应的可观测行为。

解决方案

通常来说,是的,编译器可以静态地判断,在它生成的可执行代码中,某序列原子操作的顺序是唯一可能发生的顺序,这符合现行标准。(我不认为主流的现实世界编译器会在一个包含1 秒睡眠的循环中这么做。对普通计算机而言,那是很长的一段时间,因此如果按原样编译,实际发生的那个可能执行很可能不是它。)

目前委员会的方针是,编译器不进行此类优化,直到他们找出让代码自愿参与的方法为止,而不是到处用 volatile atomic 来表示选择退出。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章