析构函数和线程安全

时间:2018-09-07 20:45:22

标签: c++ synchronization destructor

我想创建一个线程安全类,其中包含将元素插入列表的方法。 当其中一个线程破坏一个实例时,我希望处理列表中的消息,同时防止其他线程插入其他消息。

想法如下:

MyClass{
   ...
   public:
      ...
      void send(string s){
          lock_guard<mutex> lock(m); 
          my_list.push_back(s);
      }
      ~MyClass(){
          lock_guard<mutex> lock(m); 
          for(string s:my_list)
              process(s);
      }
}

同步是否正确?

对于方法send,我添加了锁,以便多个线程可以安全的方式调用它。

关于析构函数,线程是否有可能在锁定释放和实例的实际破坏之间调用send?即。 for(及其后的lock_guard销毁)是在实际销毁之前将要执行的最后一条指令,还是一旦执行了析构函数就可能出现竞争条件?

2 个答案:

答案 0 :(得分:1)

您可以分割课程:

class MyClass
{
public:
    void send(const std::string& s){
        lock_guard<mutex> lock(m); 
        my_list.push_back(s);
    }

    void process_all_messages()
    {
        lock_guard<mutex> lock(m);
        for (const string& s : my_list)
            process(s);
        //my_list.clear();
    }

    void process(const std::string& s);

// ... mutex, list, ...
};

上面有一个包装纸

class MyClassPerTHread
{
public:
    explicit MyClassPerTHread(std::shared_ptr<MyClass> ptr) : ptr(ptr) {}
    ~MyClassPerTHread(){ ptr->process_all_messages(); }

    // MyClassPerTHread(const MyClassPerTHread&);
    // MyClassPerTHread(MyClassPerTHread&&);
    // MyClassPerTHread& operator=(const MyClassPerTHread&);
    // MyClassPerTHread& operator=(MyClassPerTHread&&);

    void send(const std::string& s) { ptr->send(s); };
private:
    std::shared_ptr<MyClass> ptr;
};

因此,在main中,您将创建std::shared_ptr<MyClass>的实例。 您将其传递给每个将其包装在MyClassPerTHread中的线程。

MyClassPerTHread被销毁后,您将按预期处理消息。

您可能想使MyClassPerTHread适应移动/复制。

答案 1 :(得分:0)

您在这里有很好的直觉;析构函数中的lock_guard根本没有任何好处。

原因如下: 编写方式上,必须在创建send()的lock_guard之前完成对~MyClass()的所有调用-否则消息将不会得到处理,send()可能会使用m和my_list销毁完成后,将导致不确定的行为。 send()的调用者无法确保发生这种情况,只能确保甚至在send()开始之前就已完成对~MyClass()的所有调用。

这可以。大多数类具有(或应该具有)客户端序列化销毁的要求。也就是说,客户端必须确保在调用send()之前完成对~MyClass()的所有调用者。实际上,除非另有说明,否则所有标准库类都具有此要求。有些课程故意不要求这样做。很好,但有点异国情调。

幸运的是,对于客户而言,这确实不是一件难事。他们可以使用Jarod42建议的shared_ptr之类的东西。

tl; dr:

  

线程是否有可能在锁定释放和实例的实际破坏之间调用send?

是的!如果这样做并摆脱析构函数中的锁定,请记录这是客户端错误。