挂起的 std::将来获取 std::异步与shared_from_this参数阻止破坏它
pending std::future get of std::async with shared_from_this argument blocks destruction of this
我想创建一个类来表示一个可以异步启动的任务,并将连续运行(有效地在分离的线程中(,直到收到停止信号。为了这个问题,用法如下所示:
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;
};
但是,我发现了这段代码的一个不良功能:如果我只是wait
而不是get
未来(例如,如果我不关心method
的结果,在这种情况下无论如何都是空白(,那么当删除task
时,实例不会被销毁。
即做task->future.get()
会得到:
running
stopped
Destructor called
但task->future.wait()
给出了:
running
stopped
从阅读答案到 std::async 的参数的生命周期是什么?我相信这里的问题是this->shared_from_this()
的论点,在异步的未来变得无效(通过get
或破坏或其他方式(之前,std::async
不会被销毁。因此,此shared_ptr使类实例保持活动状态。
解决方案尝试 1:
将start
中的行替换为:
future = std::async(std::launch::async, [this]() {
return this->shared_from_this()->method();
});
这可确保它创建shared_ptr在方法完成时被销毁,但我一直担心在它被 lambda 捕获(发生在这一行,对吗?(和 lambda 在新线程中执行 lambda 的时间之间没有什么可以this
阻止它被销毁。这是真的可能吗?
解决方案尝试 2:
为了保护在 lambda 函数运行之前销毁this
(task
(,我添加了另一个成员变量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
变量,该变量在退出时被销毁。
此解决方案是否存在问题,或者我是否忽略了实现我所追求的排序功能的更简洁的方法?
谢谢!
我在某些情况下发现的解决方案尝试 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
(通过重置共享指针(是安全的 - 在承诺设置其值之前删除承诺的未来是安全的。不会发生死锁,因为在承诺尝试转移价值之前,未来就被破坏了。
欢迎对此解决方案或可能更整洁的替代方案提出任何评论。特别是很高兴知道是否有我忽略的问题。
我会建议以下解决方案之一:
解决方案 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
将来,此解决方案也会起作用,因为未来的析构函数由std::async
块返回,直到任务终止。重要的是最后构建未来,以便在所有其他成员被摧毁之前将其销毁(从而破坏块(。如果风险太大,请改用解决方案 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 这是最干净的选择。
- 带有指定长度字符* 参数的 std::regex_search 在 VS2017 中不起作用?
- 为什么 std::function 可以作为 std::not2 的参数?
- 传递给std::function template的template参数究竟代表什么
- 转换函数,将 std::数组的双精度作为参数或双精度作为参数单独转换
- 在C++中,使用带有 std::optional 参数的函数<T>来表示可选参数是否有意义?
- 将函数参数"const char*"转换为"std::string_view"是
- 当指向对象的指针作为参数传递给 std::thread 时,内存可见性
- 为什么 std::绑定错误参数可以成功?
- 使用模板化的键类型定义 std::map,该键类型基于作为参数接收的函数
- std::vector 没有重载函数的实例与参数列表匹配
- std::span<const T> 作为函数模板中的参数
- SegFault 同时使用 std::string::operator+= 和函数作为参数
- 如果模板没有可变参数,则 Lambda 被推导出为 std::function
- 将参数打包的参数传递到 std::queue 中,以便稍后使用不同的函数调用
- 在构造函数中使用可变参数初始化 std::tuple
- 为什么我不能将引用作为 std::async 的函数参数传递
- 转发变量参数列表以模拟 std::thread
- 使用 std::enable_if 限制派生类的模板参数时出现编译错误
- "std::shared_ptr":不是参数"_Ty"的有效模板类型参数
- 可变参数模板参数扩展 类型为 std::function 的类成员