C++11中有没有一种取消/分离future的方法?

46

我有以下代码:

#include <iostream>
#include <future>
#include <chrono>
#include <thread>

using namespace std;

int sleep_10s()
{
    this_thread::sleep_for(chrono::seconds(10));
    cout << "Sleeping Done\n";
    return 3;
}

int main()
{
    auto result=async(launch::async, sleep_10s);
    auto status=result.wait_for(chrono::seconds(1));
    if (status==future_status::ready)
        cout << "Success" << result.get() << "\n";
    else
        cout << "Timeout\n";
}

这段代码本应等待1秒钟,打印"Timeout"并退出。但它没有退出,反而多等了9秒钟,打印"Sleeping Done"后崩溃了。有没有办法取消或分离future,使我的代码在main函数结束时退出,而不是等待future执行完毕?


2
您的代码不应该崩溃。这听起来像是您的C++库对launch::async的实现出了问题。 - Nicol Bolas
@NicolBolas 如果没有调用std::future::get()并且到达main的结尾,会发生什么?标准是否指定了这一点?我之所以问是因为当我看到程序在退出main时没有加入std::thread(GCC 4.6或4.7)而崩溃。 - juanchopanza
8
如果在销毁std::thread时,没有加入或分离它,实现会调用std::terminate来中止程序。使用std::async可以避免这个问题,因为它会等待任务完成。 - Anthony Williams
2
我假设你正在使用GCC,如果是这样的话,那么segfault就是这个bug:http://gcc.gnu.org/PR54297 - 我将在本周末检查并修复它。 - Jonathan Wakely
这个问题:https://dev59.com/WWEi5IYBdhLWcg3wMZ3a,涉及到 OP 问题中的“detach”部分。 - Mae Milano
3个回答

33

C++11标准没有直接提供取消使用std::async启动的任务的方法。您需要实现自己的取消机制,例如传入一个原子标志变量给异步任务,定期检查该变量。

但是,请确保您的代码不会崩溃。当main函数执行结束时,result中持有的std::future<int>对象将被销毁,它将等待任务完成,然后丢弃结果并清理任何使用的资源。


3
国旗是否一定需要原子操作?比如你有一个递归搜索函数,想要在其中一个线程找到结果时取消所有线程。即使在线程A从标志中读取false后,在处理这个信息之前,另一个成功的线程B将true写入标志,也没有真正的伤害,因为在下一个递归深度中,线程A仍将取消自身。只要写入是单向的(只能从false到true),就不应该有正确性问题,甚至不会有同时写入的问题。这种推理正确吗? - TemplateRex
21
在一个线程中写入非原子变量,并在另一个线程中未进行同步的情况下读取该变量(例如,在写入和读取时都不锁定相同的互斥量)是未定义行为。“当我尝试它时,它像我预期的那样工作”是UB的一种可能结果,但在其他情况下可能会得到不同的结果(例如崩溃)。 - Anthony Williams
1
@rhalbersma:标准明确允许重新排序内存访问。例如,接收信息的线程在检查标志位之前可以先加载结果,或者发布线程在发布结果之前可以更新标志位。原子变量将禁止这种重新排序的发生。 - user283145
4
仅在写入期间使用互斥量是不够的,您必须在读取和写入时都使用相同的互斥量。如果只是一个简单的布尔变量(像这里一样),并且您无论如何都在使用C++11库,请使用"std::atomic<bool>",编译器将为您处理所有事情。 - Anthony Williams
1
@rhalbersma 是的,这是可能的。请参见http://markshroyer.com/2012/06/c-both-true-and-false/。如果您的实现将true设置为0xFFFF,false设置为0x0000之类的操作,那么在写入和读取0x00FF时,您可能已经完成了一半,这既不是true也不是false。 - m42a
显示剩余4条评论

28

以下是使用原子布尔值取消一个或多个future的简单示例。原子布尔值可能被封装在Cancellation类中(具体取决于个人喜好)。

#include <chrono>
#include <future>
#include <iostream>

using namespace std;

int long_running_task(int target, const std::atomic_bool& cancelled)
{
    // simulate a long running task for target*100ms, 
    // the task should check for cancelled often enough!
    while(target-- && !cancelled)
        this_thread::sleep_for(chrono::milliseconds(100));
    // return results to the future or raise an error 
    // in case of cancellation
    return cancelled ? 1 : 0;
}

int main()
{
    std::atomic_bool cancellation_token = ATOMIC_VAR_INIT(false);
    auto task_10_seconds= async(launch::async, 
                                long_running_task, 
                                100, 
                                std::ref(cancellation_token));
    auto task_500_milliseconds = async(launch::async, 
                                       long_running_task, 
                                       5, 
                                       std::ref(cancellation_token));
// do something else (should allow short task 
// to finish while the long task will be cancelled)
    this_thread::sleep_for(chrono::seconds(1));
// cancel
    cancellation_token = true;
// wait for cancellation/results
    cout << task_10_seconds.get() << " " 
         << task_500_milliseconds.get() << endl;
}

不错。我要找的情况比这更进一步:我想要像std::shared_future<T>一样的行为,但是删除一组std::shared_future<T>中的最后一个会导致它正在等待的线程被取消。也就是说,我想在一个线程中启动一个昂贵的计算,并且有一个或多个对象能够在结果可用时获取其结果,但如果所有这些对象都消失了,那就意味着“忘了它,我不想要那个结果”。 - Ben
@Ben,在你的情况下,似乎引用计数可以帮助。 - baol
1
@baol: atomic_bool未初始化,将定义更改为std :: atomic_bool cancellation_token = ATOMIC_VAR_INIT(false);解决了我的问题。 - schnaader

5

我知道这是一个老问题,但当搜索“detach std::future”时,它仍然出现在排名前列。我想到了一个简单的基于模板的方法来处理这个问题:

template <typename RESULT_TYPE, typename FUNCTION_TYPE>
std::future<RESULT_TYPE> startDetachedFuture(FUNCTION_TYPE func) {
    std::promise<RESULT_TYPE> pro;
    std::future<RESULT_TYPE> fut = pro.get_future();

    std::thread([func](std::promise<RESULT_TYPE> p){p.set_value(func());},
                std::move(pro)).detach();

    return fut;
}

使用方法如下:

int main(int argc, char ** argv) {
    auto returner = []{fprintf(stderr, "I LIVE!\n"); sleep(10); return 123;};

    std::future<int> myFuture = startDetachedFuture<int, decltype(returner)>(returner);
    sleep(1);
}

输出:

$ ./a.out 
I LIVE!
$

如果myFuture超出范围并被销毁,线程将继续执行它正在进行的操作,而不会因为它拥有std::promise及其共享状态而引起问题。这对于只有在某些情况下才希望忽略计算结果并继续执行的情况非常有用(我的使用案例)。
回答OP的问题:如果你到达main函数的末尾,它将退出而不等待未来完成。
如果您经常调用此项操作,则可以省去此宏,但这是不必要的。
// convenience macro to save boilerplate template code
#define START_DETACHED_FUTURE(func) \
    startDetachedFuture<decltype(func()), decltype(func)>(func)

// works like so:
auto myFuture = START_DETACHED_FUTURE(myFunc);

如果我使用带有 [&func] 的模板,然后用普通函数(非 lambda)调用它,会导致分段错误。如果我使用 [func],那么一切都正常工作。我猜问题在于 func 是局部参数,在 startDetachedFuture 返回后就消失了。 - Petr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接