使用 shared_from_this 参数等待 std::future 获取 std::async 会阻止对 this 的破坏
Posted
技术标签:
【中文标题】使用 shared_from_this 参数等待 std::future 获取 std::async 会阻止对 this 的破坏【英文标题】:pending std::future get of std::async with shared_from_this argument blocks destruction of this 【发布时间】:2020-01-31 22:02:03 【问题描述】:我想创建一个类来表示可以异步启动并连续运行(有效地在分离线程中)直到收到停止信号的任务。这个问题的用法如下所示:
auto task = std::make_shared<Task>();
task->start(); // starts the task running asynchronously
... after some time passes ...
task->stop(); // signals to stop the task
task->future.get(); // waits for task to stop running and return its result
然而,这个Task
类的一个关键特性是我不能保证将来会被等待/得到......即,在共享指针被销毁之前,最后一行可能不会被调用。
我编写的类的精简玩具版本如下(请忽略所有内容都是公开的,这只是为了示例的简单性):
class MyClass : public std::enable_shared_from_this<MyClass>
public:
~MyClass() std::cout << "Destructor called" << std::endl;
void start()
future = std::async(std::launch::async, &MyClass::method, this->shared_from_this());
void stop() m_stop = true;
void method()
std::cout << "running" << std::endl;
do
std::this_thread::sleep_for(std::chrono::seconds(1));
while(m_stop == false);
std::cout << "stopped" << std::endl;
return;
std::future<void> future;
std::atomic<bool> m_stop = false;
;
然而,我发现了这段代码的一个不受欢迎的特性:如果将来不是get
,我只是wait
(例如,如果我不关心method
的结果,在这种情况下是无论如何都是无效的),然后当task
被删除时,实例不会被销毁。
即做task->future.get()
给出:
running
stopped
Destructor called
但是task->future.wait()
给出:
running
stopped
通过阅读What is the lifetime of the arguments of std::async? 的答案,我相信这里的问题是std::async
的this->shared_from_this()
参数在异步的未来无效之前不会被销毁(通过get
或销毁或其他方式) .所以这个 shared_ptr 使类实例保持活动状态。
解决方案尝试 1:
将start
中的行替换为:
future = std::async(std::launch::async, [this]()
return this->shared_from_this()->method();
);
这确保它创建的 shared_ptr 在方法完成时被销毁,但我一直担心没有什么可以阻止 this
在被 lambda 捕获之间被销毁(这发生在这一行,对吗? ) 以及 lambda 在新线程中执行的时间。这真的有可能吗?
解决方案尝试 2:
为了保护 this
(task
) 在 lambda 函数运行之前被销毁,我添加了另一个成员变量 std::shared_ptr<MyClass> myself
然后我的启动方法可以如下所示:
myself = this->shared_from_this();
future = std::async(std::launch::async, [this]()
auto my_ptr = std::move(this->myself);
return myself->method();
);
这里的想法是myself
将确保如果我删除task
shared_ptr,我不会破坏类。然后在 lambda 内部,shared_ptr 被转移到本地的 my_ptr
变量中,该变量在退出时被销毁。
此解决方案是否存在问题,或者我是否忽略了一种更简洁的方法来实现我所追求的排序功能?
谢谢!
【问题讨论】:
重新尝试 1:“但我一直担心没有什么可以阻止它被破坏” - 你可以添加一个等待未来的析构函数吗? 一个很好的建议,但是当我尝试这个时,异步线程会在shared_from_this
调用时挂起。此外,如果我想将此逻辑用作基类的一部分,其中主要异步工作是在虚拟方法中完成的,那么在基析构函数中等待将为时已晚 - 我会确保所有派生方法都调用在他们的析构函数中等待
我认为如果你使用组合而不是继承,这个问题可能会消失
你为什么在异步调用中使用this->shared_from_this()
而不是this
?您不会遇到对象的生命周期问题,因为您在销毁之前调用了get
或wait
@MikevanDyke - 我希望行为是我不能保证会调用 get/wait ......我将在原始问题中澄清这一点。谢谢
【参考方案1】:
我会建议以下解决方案之一:
解决方案 1,使用 std::async
和 this
而不是 shared_from_this
:
class MyClass /*: public std::enable_shared_from_this<MyClass> not needed here */
public:
~MyClass() std::cout << "Destructor called" << std::endl;
void start()
future = std::async(std::launch::async, &MyClass::method, this);
void stop() m_stop = true;
void method()
std::cout << "running" << std::endl;
do
std::this_thread::sleep_for(std::chrono::seconds(1));
while(m_stop == false);
std::cout << "stopped" << std::endl;
return;
std::atomic<bool> m_stop = false;
std::future<void> future; // IMPORTANT: future constructed last, destroyed first
;
即使将来不调用wait
或get
,此解决方案也可以工作,因为destructor of a future returned by std::async
blocks 直到任务终止。最后构建未来很重要,以便在所有其他成员被销毁之前将其销毁(并因此阻塞)。如果这样做风险太大,请改用解决方案 3。
解决方案 2,像你一样使用分离的线程:
void start()
std::promise<void> p;
future = p.get_future();
std::thread(
[m = this->shared_from_this()](std::promise<void>&& p)
m->method();
p.set_value();
,
std::move(p))
.detach();
此解决方案的一个缺点:如果您有很多 MyClass
实例,您将创建很多线程,可能会导致争用。所以更好的选择是使用线程池而不是每个对象一个线程。
解决方案 3,将可执行文件与任务类分开 例如:
class ExeClass
public:
~ExeClass() std::cout << "Destructor of ExeClass" << std::endl;
void method()
std::cout << "running" << std::endl;
do
std::this_thread::sleep_for(std::chrono::seconds(1));
while (m_stop == false);
std::cout << "stopped" << std::endl;
return;
std::atomic<bool> m_stop = false;
;
class MyClass
public:
~MyClass() std::cout << "Destructor of MyClass" << std::endl;
void start()
future = std::async(std::launch::async, &ExeClass::method, exe);
void stop() exe->m_stop = true;
std::shared_ptr<ExeClass> exe = std::make_shared<ExeClass>();
std::future<void> future;
;
与解决方案 1 一样,这会在未来被销毁时阻塞,但是您不需要注意构造和销毁的顺序。 IMO 这是最干净的选择。
【讨论】:
【参考方案2】:解决方案尝试 2 我发现在某些情况下会产生死锁异常。这似乎来自异步线程,同时试图破坏未来(通过破坏类的实例),同时还试图设置未来的值。
解决方案尝试 3 - 到目前为止,这似乎通过了我的所有测试:
myself = this->shared_from_this();
std::promise<void> p;
future = p.get_future();
std::thread([this](std::promise<void>&& p)
p.set_value_at_thread_exit( myself->method() );
myself.reset();
, std::move(p)).detach();
这里的逻辑是一旦方法调用完成就可以安全地销毁myself
(通过重置共享指针)——在promise 设置其值之前删除promise 的future 是安全的。不会发生死锁,因为在 promise 尝试传输值之前,future 已被破坏。
欢迎使用此解决方案或可能更简洁的替代方案的任何 cmets。尤其是如果我忽略了一些问题会很好。
【讨论】:
以上是关于使用 shared_from_this 参数等待 std::future 获取 std::async 会阻止对 this 的破坏的主要内容,如果未能解决你的问题,请参考以下文章
shared_from_this使用boost :: asio抛出bad_weak_ptr
C++ shared_from_this() 不会让 obj 被破坏
为什么调用shared_from_this调用std :: terminate
shared_from_this()如何在派生类中工作,该派生类继承自从enabled_shared_from_this继承的基类