关于c ++初始化器的内存一致性

时间:2015-09-18 13:23:01

标签: linux memory c++ multithreading

如果我在一个线程中设置变量的值并在另一个线程中读取它,我会用锁来保护它,以确保第二个线程读取第一个线程最近设置的值:

主题1:

lock();
x=3;
unlock();

主题2:

lock();
<use the value of x>
unlock();

到目前为止,这么好。但是,假设我有一个c ++对象,它在初始值设定项中设置x的值:

theClass::theClass() : x(3) ...
theClass theInstance;

然后,我生成一个使用Instance的线程。有没有保证新生成的线程会看到x的正确值?或者是否有必要锁定实体的声明?我主要对Linux上的c ++感兴趣。

3 个答案:

答案 0 :(得分:0)

在C ++ 11之前,C ++标准对多个执行线程没什么好说的,所以不保证任何东西。

C ++ 11引入了一个内存模型,它定义了在什么情况下保证在一个线程上写入的内存对另一个线程可见。

对象的构造本身并不是跨线程同步的。在你的特殊情况下,你说你首先构造对象然后产生一个线程&#39;。如果你产生一个线程&#39;通过构造一个std::thread对象,并在同一个线程上构造一个对象x之后执行它,那么您可以保证在新生成的线程上看到x的正确值。这是因为thread构造函数的完成与线程函数的开头同步。

术语同步是一个用于定义C ++内存模型的特定术语,它值得完全理解what it means以理解更复杂的同步但对于您的情况概述事情&#39;只是工作&#39;无需任何额外的同步。

这都是假设您正在使用std::thread。如果您直接使用平台线程API,那么C ++标准对于发生的事情无话可说,但在实践中,您可以假设它可以在不需要锁定任何我知道的平台的情况下工作。

答案 1 :(得分:0)

你似乎对锁有误解:

  

如果我在一个线程中设置变量的值并在另一个线程中读取它,   我用锁保护它以确保第二个线程读取   最近由第一个设定的值。

这是不正确的。锁用于防止数据争用。锁定不会在线程2的指令之前安排线程1的指令。锁定到位后,线程2仍然可以在线程1之前运行并且之前读取x的值线程1更改x的值。

关于你的问题:

  1. 如果theInstance 的初始化发生在某个线程A的初始化/启动之前,则线程A保证看到x的正确值。 / LI>

    示例

    #include <thread>
    #include <assert.h>
    struct C
    {
        C(int x) : x_{ x } {}
        int x_;
    };
    
    void f(C const& c)
    {
        assert(c.x_ == 42);
    }
    
    int main()
    {
        C c{ 42 };                       // A
        std::thread t{ f, std::ref(c) }; // B
        t.join();
    }
    

    在同一个线程中:A B之前排序,因此A B之前发生。线程t中的断言将永远不会触发。

    1. 如果您初始化&#39; theInstance&#39; 线程发生在某个线程A使用之前,然后线程A保证看到x的正确值。
    2. 示例

      #include <thread>
      #include <atomic>
      #include <assert.h>
      
      struct C
      {
          int x_;
      };
      
      std::atomic<bool> is_init;
      
      void f0(C& c)
      {
          c.x_ = 37;               // B
          is_init.store(true);     // C
      }
      
      void f1(C const& c)
      {
          while (!is_init.load()); // D
      
          assert(c.x_ == 37);      // E
      }
      
      int main()
      {
          is_init.store(false); // A
          C c;
          std::thread t0{ f0, std::ref(c) };
          std::thread t1{ f1, std::ref(c) };
          t0.join();
          t1.join();
      }
      

      线程发生在t0t1之间发生关系之前。和以前一样, A 在创建线程t0t1之前发生。

      作业c.x_ = 37 B )在商店之前发生到is_init标志( C )。 f1中的循环是线程间发生 - 之前关系的来源:f1仅在is_init设置后进行,因此 C 发生在电子即可。由于这些关系是可传递的, B 线程间会在 D 之前发生。因此,断言永远不会在f1中触发。

答案 2 :(得分:-1)

首先,上面的示例并不保证任何锁定。您需要做的就是声明变量原子。没有锁,没有后顾之忧。

其次,你的问题并没有真正发挥很大作用。由于在构造之前不能使用对象(类的实例),并且构造在单个线程内发生,因此不需要锁定在类构造函数中完成的任何操作。你根本无法从多个线程访问非构造类,这是不可能的。