首页 > 解决方案 > std::async 的非阻塞调用:这个版本有多危险?

问题描述

前段时间我正在寻找一种std::async无需存储即可调用的方法std::future,从而不会在作用域结束时阻塞执行。我发现这个答案使用捕获std::shared_ptrstd::future,因此允许对 进行非阻塞调用std::async

另一种延迟析构函数调用的方法是完全阻止它被调用。这可以通过就地施工来实现operator new

考虑这个版本,它使用静态线程本地存储进行就地构造std::future<void>

template <class F>
void call_async(F&& fun) {
    thread_local uint8_t buf[sizeof(std::future<void>)] = {0};
    auto fut = new(buf) std::future<void>();
    *fut = std::async(std::launch::async, [fun]() {
        fun();
    });
}

这个版本不会产生任何与堆分配相关的开销,但它似乎非常非法,尽管我不确定具体原因。

我知道在构造对象之前使用它是UB,事实并非如此。我不确定为什么delete在这种情况下不调用会在 UB 中解决(对于堆分配,它不是 UB)。

我看到的可能问题:

https://ideone.com/C44cfe

更新

直接在静态存储中构造一个对象(正如评论中提到的 IlCapitano)将在每次调用移动分配时阻塞(共享状态将被破坏,阻塞已删除对它的最后引用的线程)。

由于未释放对共享状态的引用,不调用析构函数将导致泄漏。

标签: c++asynchronouslanguage-lawyerundefined-behavior

解决方案


在不调用它的析构函数的情况下结束非平凡对象的生命周期是未定义的行为,一旦有第二次call_async调用就会发生这种情况。

如果唯一的选择是未定义的行为,则“与堆分配相关的开销”是用词不当。future返回的人async必须住在某个地方

更新后的代码已经定义了行为:它在启动下一个调用之前等待上一个调用完成。


推荐阅读