析构函数和异步任务

Destructors and asynchronous tasks

本文关键字:任务 异步 析构函数      更新时间:2023-10-16

我有一个类,它在构造函数中使用std::async调用异步任务以加载其内容。(我希望异步加载对象)

代码如下:

void loadObject(Object* object)
{
// ... load object
}
Object::Object(): 
{
auto future = std::async(std::launch::async, loadObject, this);
}

我在我的主线程上有几个创建和删除这些对象的实例,它们可以随时被删除,甚至在加载完成之前。

我想知道当对象仍在另一个线程上处理时,将其销毁是否有危险。如果对象被破坏,我该如何停止线程?

EDIT:由于存在错误,std::future析构函数不会用我正在使用的VS2013编译器阻止我的代码。

正如MikeMB已经提到的,在加载完成之前,构造函数不会完成。检查这个问题以了解如何克服这个问题:我可以在不等待未来限制的情况下使用std::async吗?

我想知道当对象仍在另一个线程上处理时,销毁它是否有危险。

删除后访问对象的内存肯定是危险的。行为将是不明确的。

如果对象被破坏,我如何停止线程?

我建议您首先注意的是,确保对象在被要使用它的东西指向时不会被破坏。

一种方法是使用表示已完成加载的成员标志,该标志在异步任务中更新并在析构函数中检查,并将访问与条件变量同步。这将允许析构函数阻塞,直到异步任务完成。

一旦您设法防止对象被销毁,您就可以使用另一个同步成员标志来表示对象正在被销毁,并跳过加载(如果已设置)。这会增加同步开销,但如果加载成本很高,这可能是值得的。

另一种避免阻塞析构函数的方法是将std::shared_ptr传递给异步任务,并要求所有Object实例都由一个共享指针拥有。这种限制可能不是很理想,您需要继承std::enable_shared_from_this才能在构造函数中获得共享指针。

您的代码中没有任何异步发生,因为构造函数会阻塞,直到loadObject()返回(std::async返回的future的析构函数隐式联接)。

如果没有,这将取决于您如何编写代码(尤其是析构函数),但最有可能的是,您的代码会产生未定义的行为。

当对象仍在另一个线程上处理时,将其销毁是危险的

实际上,您可以根据需求和期望的行为来实施许多策略。

我会在这里实现某种皮条策略,这意味着所有实际数据都将存储在对象所持有的指针中。您将把所有数据加载到数据指针对象,并以原子方式将其存储在公共对象中。

施工结束时,技术性物体应完全收缩并准备使用。在您的情况下,数据指针对象可能仍然无法使用。你应该让你的类正确地处理这种状态。

所以我们开始了:

class Object
{
std::shared_ptr<Object_data> d;
Object::Object(): 
d(std::make_shared<Object_data>())
{
some_futures_matser.add_future(std::async(std::launch::async, loadObject, d));
}
}

然后,在数据对象中制作原子标志,表示加载已完成,对象已准备好使用。

class Object_data
{
// ...
std::atomic<bool> loaded {false};
};
loadObject(std::shared_ptr<Object_data> d)
{
/// some load code here
d->loaded = true;
}

每次通过loaded标志访问对象(使用线程安全的方式)时,都必须检查对象是否受到约束