3

I just compiled a project I've been working on under Windows for Linux and found that it hangs at a certain point. Since I am using std::async and std::mutex my first assumption was, that it could be a deadlock problem. However, then I wonder why it runs fine on Windows. Here is the code:

void BorderExtractor::preprocessImageAsync(const PreprocessingSettings& settings) {
    _preprocessingMutex.lock();
    if (!_preprocessingActive) {
        _preprocessingActive = true;
        std::async(std::launch::async, &BorderExtractor::preprocessImage, this, settings);
        //this point is never reached on linux
        _preprocessingUpToDate = true;
    } else {
        _cachedSettings = settings;
        _preprocessingUpToDate = false;
    }
    _preprocessingMutex.unlock();
}

This is the function that never returns under Linux. It runs until the async call and then it just stops. It nearly appears as if the function wasn't launched asynchronously and the program waits for it to return, what wouldn't work, because the other function will try to lock the same mutex.

Here is the function that's called asynchronously:

void BorderExtractor::preprocessImage(PreprocessingSettings settings) {

    //here some image processing stuff is done

    _preprocessingMutex.lock();
    //this point is never reached on linux
    if (!_preprocessingUpToDate) {
        _preprocessingUpToDate = true;
        _preprocessingMutex.unlock();
        std::async(std::launch::async, &BorderExtractor::preprocessImage, this, _cachedSettings);
    } else {
        _preprocessingUpToDate = true;
        _preprocessingActive = false;
        _preprocessingMutex.unlock();
    }
}

The point after it tries to lock the mutex is never reached under linux.

Now, what is the problem? Is it my code that is faulty, or is there something special I have to pay attention to on Linux? (compiler flags, etc) To me this appears as if the async call is synchronous and thus causes a deadlock. But why should that be

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
bweber
  • 3,772
  • 3
  • 32
  • 57
  • 1
    Which compiler are you using? Perhaps unrelated, but the future returned by `std::async()` joins on destruction, so if your library implementation is conforming, this call is executed synchronously: `std::async(std::launch::async, &BorderExtractor::preprocessImage, this, settings)`. AFAIK in MS's implementation the future's destructor does not join. – Andy Prowl Jan 16 '15 at 19:59
  • I am using GCC. Ok, I see the problem. Is there a way around apart from creating a member variable to save the future? – bweber Jan 16 '15 at 20:13
  • Are you using GCC on Windows too? – Andy Prowl Jan 16 '15 at 20:13
  • No, am using VS2013 compiler there. – bweber Jan 16 '15 at 20:14
  • OK, the situation is clear then. See my answer for the details. – Andy Prowl Jan 16 '15 at 20:15

1 Answers1

5

This call:

async(std::launch::async, &BorderExtractor::preprocessImage, this, _cachedSettings);

Effectively runs synchronously. This is due to the fact that the destructor of an std::future returned by std::async() ends up joining with the asynchronous computation - notice, that the behavior would be different if you obtained the future in other ways.

Since you are not keeping the future object returned by std::async alive, its lifetime ends immediately after the function call returns, and its destructor blocks until the asynchronous computation terminates - which is forever, as this seems to cause a deadlock.

The reason why this works on Windows may be due to the fact that you are using a non-compliant implementation of the Standard Library (e.g. Microsoft's implementation that comes with VS2013), in which the future's destructor does not join with the asynchronous computation - MS did this intentionally, following the rationale illustrated in this (rejected) proposal by Herb Sutter.

If you are looking for a fire-and-forget approach, consider this alternative implementation of std::async(), which does not cause the returned future to block on destruction (courtesy of bamboon):

template<class Function, class... Args>
std::future<typename std::result_of<Function(Args...)>::type> async( 
    Function&& f, 
    Args&&... args) 
{
    using R = typename std::result_of<Function(Args...)>::type;
    auto bound_task = std::bind(std::forward<Function>(f), std::forward<Args>(args)...);
    auto task = std::packaged_task<R()>{std::move(bound_task)};
    auto ret = task.get_future();
    auto t = std::thread{std::move(task)};
    t.detach();
    return ret;   
}

As a side note, avoid explicitly locking/unlocking mutexes. Rather, use RAII wrappers like std::lock_guard or (if necessary) std::unique_lock to make sure your mutex will be unlocked even if an exception is thrown or in case of an early return:

// The mutex will be unlocked automatically when the function returns.
std::lock_guard<std::mutex> lock{_preprocessingMutex};

if (!_preprocessingUpToDate) {
    _preprocessingUpToDate = true;
    async(std::launch::async, &BorderExtractor::preprocessImage, this, _cachedSettings);
    // No need to call unlock() on the mutex!
} else {
    _preprocessingUpToDate = true;
    _preprocessingActive = false;
    // No need to call unlock() on the mutex!
}
Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • What do you mean by "Notice, that the behavior would be different if you obtained the future in other ways."? – bweber Jan 16 '15 at 20:31
  • @user1488118: Only futures returned by `std::async()` join on destruction. For an alternative scenario where the future does not block, see the answer I linked. – Andy Prowl Jan 16 '15 at 20:32
  • Ok, I tried two things. First, changing the async into `std::thread(&BorderExtractor::preprocessImage, this, settings).detach();` which led to a crash with the error "Abort() was called" when using the program (Windows). Second, I tried the template function suggested in the other thread, but it won't compile, giving the compiler error "term does not evaluate to a function taking 3 arguments". The problem could be that I am trying to call a function on an object (so I have to pass a pointer to the object). Unfortunately I don't really know much about template programming. – bweber Jan 16 '15 at 21:16
  • @user1488118: The reason why it fails to compile on Windows is probably a bug in MS's implementation of `std::packaged_task<>` (just FYI, [here](http://stackoverflow.com/a/19431195/1932150) you can find a patch), but that's not relevant, since MS's implementation of `std::async()` already does what you want. What you can do is write a wrapper `async()` function that, depending on a compilation constant (e.g. `#ifdef _WINDOWS`) calls `std::async()` on Windows, and the modified version of `async()` I posted in my answer on Linux. – Andy Prowl Jan 16 '15 at 21:24
  • @user1488118: P.S. [Live demo](http://coliru.stacked-crooked.com/a/29ccfa31ffd08088) of the alternative `async()` compiling on Linux with GCC 4.9. – Andy Prowl Jan 16 '15 at 21:26
  • Somehow I got it to work now by changing the async call into `std::thread(&BorderExtractor::preprocessImage, this, settings).detach();` Thank you for your help! – bweber Jan 16 '15 at 22:09
  • @user1488118: Ok, glad to hear you managed. However, keep in mind that if `preprocessImage()` will throw an exception, this approach (i.e. starting it on an `std::thread` which is then detached) will terminate your application. That would not be the case if you used the alternative version of `async()`. If `preprocessImage()` won't throw an exception, then it should make no difference. – Andy Prowl Jan 16 '15 at 22:19