【问题标题】:Why g++ O2 option make unsigned wrap around not working?为什么 g++ O2 选项使无符号环绕不起作用?
【发布时间】:2022-01-08 17:30:18
【问题描述】:

我试图用 c++ 编写一个队列,我从 intel dpdk libring 中了解到,我可以通过使用 unsigned wrap around 属性编写类似的代码来做到这一点:

#include <cstdio>
#include <cassert>
#include <atomic>
#include <thread>

size_t global_r = 0, global_w = 0, mask_ = 3;

void emplace() {
  unsigned long local_w, local_r, free_entries = 0;
  local_w = global_w;
  while (free_entries == 0) {
    local_r = global_r;
    free_entries = (mask_ + local_r - local_w);
  }
  fprintf(stderr, "%lu\n", free_entries);
  auto w_next = local_w + 1;
  std::atomic_thread_fence(std::memory_order_release);
  global_w = w_next;
}

void pop() {
  unsigned long local_r = global_r;
  unsigned long r_next = local_r + 1;
  // make sure nobody can write to it before destruction
  std::atomic_thread_fence(std::memory_order_release);
  global_r = r_next;
}

int main() {
  std::jthread([]() -> void {
    int i = 10;
    while (i-- >= 0) emplace();
  });
  std::jthread([]() -> void {
    int i = 10;
    while (i-- >= 0) pop();
  });
  return 0;
}

当我使用 g++ O0 和 O2 运行它时,它会产生不同的结果: 与 O2:

3
2
1
0
18446744073709551615
18446744073709551614
18446744073709551613
18446744073709551612
18446744073709551611
18446744073709551610
18446744073709551609

没有氧气:

3
2
1
.....long time suspending

我想知道我对 unsinged wrap around 的理解有什么问题吗? (我从几个 stackoverflow 帖子和其他参考资料中了解到,未分割的环绕是已定义的行为)。

【问题讨论】:

    标签: c++ g++ unsigned-integer


    【解决方案1】:

    您是否知道一旦global_w 增加到3,那么emplace() 中的while 循环就会变成无限循环? AFAIK,无限循环会导致 C++ 中的未定义行为

    我相信您的问题来自您将 std::jthread 对象定义为临时对象的事实。这意味着它们在它们出现的表达结束时被破坏。因此,两个线程不会(同时)并行运行。

    您可以通过创建线程变量来轻松更改它,这些变量将在main() 的末尾被破坏:

    int main()
    {
      std::thread t1 ([]() -> void {  // note that "t1" variable name
        int i = 10;
        while (i-- >= 0) emplace();
      });
    
      std::thread t2 ([]() -> void {  // note that "t2" variable name
        int i = 10;
        while (i-- >= 0) pop();
      });
    }
    

    然而,即便如此,我认为您在global_r 上存在数据竞争,这也会导致未定义的行为。如果没有同步写入,编译器很容易假设 emplace() 拥有对 global_r 的独占访问权限,并有效地从循环中“删除”这个读取 local_r = global_r;

    此类问题的现场演示:https://godbolt.org/z/566WP9n36

    【讨论】:

    • 非常感谢,之前没有想过像UB那样的无限循环和数据竞争,我只是在dpdk中模仿了环码,好像漏掉了一些同步的关键代码片段.
    猜你喜欢
    • 1970-01-01
    • 2012-02-29
    • 1970-01-01
    • 2020-01-03
    • 2019-10-25
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 2015-11-02
    相关资源
    最近更新 更多