【问题标题】:Trying to access an object that is being destroyed试图访问正在被销毁的对象
【发布时间】:2016-03-02 09:17:31
【问题描述】:

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

#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处发生段错误。我该如何解决这个问题?

【问题讨论】:

  • 您将遇到分段错误,因为您尝试取消引用 nullptr。调用 StopA 会将 a 设置为 nullptr。 GetA 取消引用它return *a

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


【解决方案1】:

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

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

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


这里的一些代码看起来格格不入或晦涩难懂,例如a = std::make_unique&lt;A&gt;(*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 保持原样。

【讨论】:

  • 好的,我正在尝试应用您的修复程序,不幸的是实际代码有点复杂,现在我在thread.join() 中遇到异常,如果需要更多帮助,我会更新问题.
  • @FaTony。好的,那可能是它不再是joinable(),这意味着其他东西已经加入它,或者它已经分离。注意正在移动的线程以及您加入的不再代表该线程的对象。
  • 好的,现在我仍然在GetA 遇到段错误,而另一个线程在A 构造函数中。
  • 好的,我通过将线程启动代码移动到单独的 Start 函数来修复它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-23
  • 2012-04-24
相关资源
最近更新 更多