【问题标题】:Do wee need a lock for uint64_t in single writer multi-reader situation?在单写多读情况下,我们是否需要为 uint64_t 加锁?
【发布时间】:2020-09-08 07:50:35
【问题描述】:
class Foo {
public:
    void set(uint64_t new_var) { var_ = new_var; }
    uint64_t get() { return var_; }
private:
    uint64_t var_;
};

如果一个作家多读者,是否需要锁定setget

【问题讨论】:

  • 这取决于代码运行的平台,但为了安全起见,您可以使用std::atomic<uint64_t>
  • 除非声明 _Atomic,否则不能假定 C 中的变量具有原子访问权限。 CPU 数据宽度无关紧要,写入者的数量无关紧要。如果写入发生在多个指令中(例如“从堆栈加载寄存器”、“将值写入寄存器”)并在中途被中断,那么您就有竞争条件错误。在某些情况下,这可能只会导致不正确的计时错误,在其他情况下会损坏数据。

标签: c++ c concurrency


【解决方案1】:

如果一个作家多读者,是否需要锁定setget

你不一定需要一个,但你需要 同步,这可以通过锁一个mutex 或简单地声明变量 atomic

在 C++ 中:std::atomic<uint64_t> var_;

在 C 中:_Atomic uint64_t var_;

无论数据类型如何,在 C 和 C++ 语言中,当涉及至少一个写入器时,同时访问的每个变量都需要同步

这与编译器在生成代码时对指令的排序方式有关;在没有同步的情况下,根据as-if rule,允许编译器假设每个线程都是独立执行的,并以它认为合适的任何方式重新排序变量访问。由于无法预测结果,非同步访问会立即将程序置于未定义行为领域。例如,变量可以优化到 CPU 寄存器中,两个线程根本不会注意到它的变化,或者它可能在程序中的不同时间写入内存。

此外,在许多 CPU 架构(不包括 x86)上,64 位读/写实际上不是原子的。所以即使代码的布局恰到好处,它也可能无法正常工作。

【讨论】:

    【解决方案2】:

    是的,这是必要的。 除了平台限制(见评论),你的 uint64_t 可以不对齐:读取可以请求 2 个操作。

    【讨论】:

      【解决方案3】:

      是的,考虑这个例子:

      #include <iostream>
      #include <future>
      #include <vector>
      
      class Foo
      {
      public:
          void set(uint64_t new_var) { var_ = new_var; }
          uint64_t get() { return var_; }
      
      private:
          uint64_t var_;
      };
      
      int main()
      {
          Foo v1; v1.set(0);
          uint64_t v2 = 0;
      
          std::vector<std::future<void>> ret_1;
          ret_1.reserve(10);
          std::vector<std::future<void>> ret_2;
          ret_2.reserve(10);
      
          for (int i = 0; i < 10; i++)
          {
              ret_1.push_back(std::async(std::launch::async, [&]() {
                  for (int j = 0; j < 10'000; j++)
                      v1.set(v1.get() + 1ul);
              }));
              ret_2.push_back(std::async(std::launch::async, [&]() { 
                  for (int j = 0; j < 10'000; j++)
                      v2+=1ul; 
              }));
          }
      
          for(auto& f : ret_1)
              f.wait();
      
          for(auto& f : ret_2)
              f.wait();
      
          // Expected in both examples: 10'000 * 10 = 100'000
          std::cout << "Foo: " << v1.get() << std::endl;
          std::cout << "uint64_t: " << v2 << std::endl;
      
      }
      

      预期结果是 100'000,但这是输出:

      raidg@Papuga:~$ c++ -o main main.cpp -lpthread
      raidg@Papuga:~$ ./main 
      Foo: 26257
      uint64_t: 32825
      

      因为它们之间没有同步

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多