通过加入析构函数来等待异步工作是否安全?

时间:2017-09-12 16:07:45

标签: c++ multithreading c++11 thread-safety object-lifetime

假设我有一个可以异步运行某些代码的类,并且该异步代码使用该类实例来执行诸如调用成员函数,读取数据成员等操作。显然,类实例必须比后台线程更长,以便那些访问是安全的。通过在析构函数中加入后台线程来确保这一点就足够了吗?例如:

#include <iostream>
#include <thread>

class foo final
{
public:
    foo() = default;

    void bar() {
        std::cout << "Hopefully there's nothing wrong with using " << this << "\n";
    }

    void bar_async() {
        if (!m_thread.joinable()) {
            m_thread = std::thread{&foo::bar, this};
        }
    }

    ~foo() {
        if (m_thread.joinable()) {
            std::cout << "Waiting for " << m_thread.get_id() << "\n";
            m_thread.join();
        }
    }

private:
    std::thread m_thread;
};

int main() {
    foo f;
    f.bar_async();
}

具体来说,我担心object lifetime rules

  

对于析构函数不是很简单的类类型的任何对象,生命周期在析构函数的执行开始时结束。

     

...在对象的生命周期结束之后,在重用或释放对象占用的存储之前,以下使用标识该对象的glvalue表达式是未定义的:...

     
      
  • 访问非静态数据成员或调用非静态成员函数。
  •   

但是对我来说,严格阅读上述内容也意味着直接从this->bar()内部调用~foo()是未定义的,“显然”并非如此。

2 个答案:

答案 0 :(得分:3)

cppreference是正确的,但它讨论的是从对象访问成员,而不是从析构函数内部访问。如果我们看一下[class.cdtor] / 1,我们就会看到

  

对于具有非平凡构造函数的对象,在构造函数开始执行之前引用该对象的任何非静态成员或基类会导致未定义的行为。对于具有非平凡析构函数的对象,在析构函数完成执行后引用对象的任何非静态成员或基类会导致未定义的行为

强调我的

因此,只要我们在析构函数中,我们仍然可以使用成员对象,因为在析构函数的范围结束之前,这些对象不会被销毁。

因此,在线程上调用join就可以了。如果你不考虑它,那么如果访问他们所指的互斥锁是未定义的行为,那么锁定保护之类的东西将毫无用处。

答案 1 :(得分:0)

我的直觉不是。这是因为thread :: join可以抛出异常,并且您不希望异常转义析构函数。如果你将它包装在try catch中并且正确处理异常可能没问题。