Is there a way to cancel/detach a future in C++11?
Asked Answered
C

3

46

I have the following code:

#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";
}

This is supposed to wait 1 second, print "Timeout", and exit. Instead of exiting, it waits an additional 9 seconds, prints "Sleeping Done", and then segfaults. Is there a way to cancel or detach the future so my code will exit at the end of main instead of waiting for the future to finish executing?

Cutie answered 23/8, 2012 at 7:20 Comment(5)
Your code shouldn't crash. That sounds like a problem with your C++ library's implementation of launch::async.Conventicle
@NicolBolas what should happen if there is no call to std::future::get() and you reach the end of main? Does the standard specify that? I ask because when I have seen programs crash when exiting main without joining an std::thread (GCC 4.6 or 4.7).Ringtail
If you haven't joined or detached a std::thread when you destroy it, the implementation calls std::terminate to abort the program. Using std::async avoids this problem --- it waits for the task to complete.Medlock
I assume you're using GCC, in which case the segfault is this bug: gcc.gnu.org/PR54297 - I'm checking in a fix this weekend.Falster
This question: #21531596 deals with the "detach" part of the OP's question.Biquarterly
M
33

The C++11 standard does not provide a direct way to cancel a task started with std::async. You will have to implement your own cancellation mechanism, such as passing in an atomic flag variable to the async task which is periodically checked.

Your code should not crash though. On reaching the end of main, the std::future<int> object held in result is destroyed, which will wait for the task to finish, and then discard the result, cleaning up any resources used.

Medlock answered 23/8, 2012 at 7:56 Comment(9)
Does the flag really have to be atomic? Say if you have a recursive search function, and want to cancel all threads whenever one of them finds a result. Even if just after reading false from the flag, but before processing that information, within thread A, a successful thread B writes true to the flag, there is no real harm done because on the next recursion depth thread A will still cancel itself. As long as writing is one-way (only from false to true) there should not be a correctness problem, not even from simultaneous writes. Is this reasoning correct?Geosyncline
It needs to be atomic to ensure it always has a valid state. On a theoretical machine it might be possible to halfway write a bool so that when you check it it's neither true nor false, and then I believe reading it is undefined behavior. Even if it's not UB it might not be what the program expects.Cutie
Writing a non-atomic variable from one thread, and reading that variable from another thread without synchronization (e.g. locking the same mutex on both write and read) is undefined behaviour. "It works like I expect when I try it" is one possible result from UB, but you might get a different outcome (such as a crash) on other occasions.Medlock
@rhalbersma: The standard specifically allows to reorder memory accesses. For example, a thread receiving the information could load the results before checking the flag, or the publishing thread could update the flag before publishing the results. An atomic variable would disallow such reorderings to happen.Waxy
@Cutie "neither true nor false", is that really possible? I understand the UB means that you can't know whether it is true or false, but neither seems totally weird.Geosyncline
@AnthonyWilliams So what is the correct approach? Is it enough to only have a mutex during writes? I guess I should buy your book..Geosyncline
It is not enough to use a mutex only during writes --- you must use the same mutex for both reads and writes. If it's just a simple boolean variable (like here), and you're using the C++11 library anyway, just use std::atomic<bool> and the compiler will take care of everything for you.Medlock
@rhalbersma Yep, it's possible. See markshroyer.com/2012/06/c-both-true-and-false . And if your implementation does something like set true to 0xFFFF and false to 0x0000, you can be halfway through a write and read 0x00FF, which is neither true nor false.Cutie
@rhalbersma : The standard actually mentions this, though in a non-normative footnote: "Using a bool value in ways described by this International Standard as “undefined,” such as by examining the value of an uninitialized automatic object, might cause it to behave as if it is neither true nor false."Roentgenogram
M
28

Here a simple example using an atomic bool to cancel one or multiple future at the same time. The atomic bool may be wrapped inside a Cancellation class (depending on taste).

#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;
}
Mastic answered 24/10, 2015 at 6:21 Comment(3)
Nice. The case I’m looking for is one step beyond this: I want behavior that is like a std::shared_future<T> but where deleting the last of a group of std::shared_future<T>s causes the thread it is waiting on to be canceled. That is, I want to kick off an expensive computation in a thread and have one or more objects able to get its result when it’s available, but if all of those objects go away, that means “forget it, I don’t want that result”.Tenor
@Tenor in your case it seems that reference counting could helpMastic
@baol: The atomic_bool is uninitialized, changing the definition to std::atomic_bool cancellation_token = ATOMIC_VAR_INIT(false); fixed this for me.Fosque
S
5

I know this is an old question, but it still comes up as the top result for "detach std::future" when searching. I came up with a simple template based approach to handle this:

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;
}

and you use it like so:

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);
}

output:

$ ./a.out 
I LIVE!
$

If myFuture goes out of scope and is destructed, the thread will carry on doing whatever it was doing without causing problems because it owns the std::promise and its shared state. Good for occasions where you only sometimes would prefer to ignore the result of a computation and move on (my use case).

To the OP's question: if you get to the end of main it will exit without waiting for the future to finish.

This macro is unnecessary but saves on typing if you are going to call this frequently.

// 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);
Simplex answered 12/2, 2020 at 21:52 Comment(1)
If I use your template with [&func] and then call It with ordinary function (no lambda), it ends with segmentation fault. If I use it with [func], then everything works fine. I guess the problem is that func is local parameter which goes away after startDetachedFuture returns.Sharell

© 2022 - 2024 — McMap. All rights reserved.