试图访问正在销毁的对象

时间:2016-03-02 09:17:31

标签: c++ multithreading c++11 c++14

我有一个对象,其中包含一个间接访问此对象的线程,如下所示:

#include <iostream>
#include <thread>
#include <atomic>

class A;

class Manager
{
public:
    Manager(void) = default;
    void StartA(void)
    {
        a = std::make_unique<A>(*this);
    }
    void StopA(void)
    {
        a = nullptr;
    }
    A& GetA(void)
    {
        return *a;
    }
private:
    std::unique_ptr<A> a;
};

class A
{
public:
    A(Manager& manager)
        : manager{manager},
        shouldwork{true},
        thread{[&]{ this->Run(); }}
    {
    }
    ~A(void)
    {
        shouldwork = false;
        thread.join();
    }
private:
    Manager& manager;
    std::atomic<bool> shouldwork;
    std::thread thread;
    void Run(void)
    {
        while (shouldwork)
        {
            // Here goes a lot of code which calls manager.GetA().
            auto& a = manager.GetA();
        }
    }
};

int main(int argc, char* argv[])
try
{
    Manager man;
    man.StartA();
    man.StopA();
}
catch (std::exception& e)
{
    std::cerr << "Exception caught: " << e.what() << '\n';
}
catch (...)
{
    std::cerr << "Unknown exception.\n";
}

问题是当一个线程调用Manager::StopA并输入A的析构函数时,A内的线程会在Manager::GetA处发生段错误。我该如何解决这个问题?

1 个答案:

答案 0 :(得分:3)

StopA()中设置a = nullptr;,这反过来会破坏a对象,并且对其成员的所有进一步访问都会导致未定义的行为(可能导致分段错误)。

只需将a = nullptr;移动到Manager的析构函数即可解决此问题。更好的是,当std::unique_ptr的析构函数运行时(即完全删除代码行),允许a的RAII机制销毁Manager对象。

使用active object implementations,仔细控制成员变量很重要,尤其是&#34;停止变量/控制&#34; (这里是shouldwork = false;)。允许管理器直接或通过方法访问变量,以在活动对象销毁之前停止它。

这里的一些代码看起来不合适或模糊不清,例如a = std::make_unique<A>(*this);。重新设计可以帮助简化一些代码。可以删除Manager类。

class A
{
public:
    A(): shouldwork{true}, thread{[&]{ this->Run(); }}
    {
    }
    void StopA()
    {
        shouldwork = false;
        thread.join(); 
    }
private:
    std::atomic<bool> shouldwork;
    std::thread thread;
    void Run(void)
    {
        while (shouldwork)
        {
            // code...
        }
    }
};

代码是按std::thread的方式建模的,如果在尝试加入之前,胎面的停止受到更多控制。在这种情况下,析构函数保持为空,以模仿终止(调用std::terminate)结果,就像标准线程库的情况一样。必须在销毁之前显式连接(或分离)线程。

重新引入Manager,代码可能如下所示;

class A
{
public:
    A() : shouldwork{true}, thread{[&]{ this->Run(); }} {}
    void StopA() { shouldwork = false; thread.join(); }
private:
    void Run();
    std::atomic<bool> shouldwork;
    std::thread thread;
};

class Manager
{
public:
    Manager() = default;
    void StartA(void)
    {
        a = std::make_unique<A>();
    }
    void StopA(void)
    {
        a->StopA();
    }
    A& GetA(void)
    {
        return *a;
    }
private:
    std::unique_ptr<A> a;
};

void A::Run()
{
    while (shouldwork)
    {
        // Here goes a lot of code which calls manager.GetA().
        auto& a = manager.GetA();
    }
}

您的main仍然保持不变。