9

I'm learning about mutexes in C++ and have a problem with the following code (taken from N. Josuttis' "The C++ Standard Library").

I don't understand why it blocks / throws unless I add this_thread::sleep_for in the main thread (then it doesn't block and all three calls are carried out).

The compiler is cl.exe used from the command line.

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

std::mutex printMutex;

void print(const std::string& s)
{
    std::lock_guard<std::mutex> lg(printMutex);

    for (char c : s)
    {
        std::cout.put(c);
    }
    std::cout << std::endl;
}

int main()
{
    auto f1 = std::async(std::launch::async, print, "Hello from thread 1");
    auto f2 = std::async(std::launch::async, print, "Hello from thread 2");

    // std::this_thread::sleep_for(std::chrono::seconds(1));

    print(std::string("Hello from main"));       
}
Niall
  • 30,036
  • 10
  • 99
  • 142
Wojtek
  • 801
  • 1
  • 9
  • 13
  • 3
    You aren't waiting for the threads to finish. I think this is an issue with the MSVC library here. – Niall Oct 09 '14 at 12:49

3 Answers3

11

I think what you are seeing is an issue with the conformance of the MSVC implementation of async (in combination with future). I believe it is not conformant. I am able to reproduce it with VS2013, but unable to reproduce the issue with gcc.

The crash is because the main thread exits (and starts to clean up) before the other two threads complete.

Hence a simple delay (the sleep_for) or .get() or .wait() on the two futures should fix it for you. So the modified main could look like;

int main()
{
    auto f1 = std::async(std::launch::async, print, "Hello from thread 1");
    auto f2 = std::async(std::launch::async, print, "Hello from thread 2");

    print(std::string("Hello from main"));       

    f1.get();
    f2.get();
}

Favour the explicit wait or get over the timed "sleep".

Notes on the conformance

There was a proposal from Herb Sutter to change the wait or block on the shared state of the future returned from async. This may be the reason for the behaviour in MSVC, it could be seen as having implemented the proposal. I'm not sure what the final result was of the proposal was or its integration (or part thereof) into C++14. At least w.r.t. the blocking of the future returned from async it looks like the MSVC behaviour did not make it into the specification.

It is interesting to note that the wording in §30.6.8/5 changed;

From C++11

a call to a waiting function on an asynchronous return object that shares the shared state created by this async call shall block until the associated thread has completed, as if joined

To C++14

a call to a waiting function on an asynchronous return object that shares the shared state created by this async call shall block until the associated thread has completed, as if joined, or else time out

I'm not sure how the "time out" would be specified, I would imagine it is implementation defined.

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
  • So whats the expected behavior? Threads running with no reference to them, perhaps after the main thread has terminated, should keep running, similar to the turkey with its head cut off – Mikhail Oct 16 '14 at 08:09
  • @Mikhail. I like the turkey analogy, lol. I think this is why the proposal was probably rejected (at least the part where the threads are disassociated from any wait state). I think it is a better idea for the `future` returned from the `async` to block. I also think there is sometimes confusion around the source of a `future` and whether it will block or not - maybe an `async_future` may have been a better idea, but I don't know. I believe `async` was a late addition, so may they didn't iron out all of the more subtle issues. – Niall Oct 16 '14 at 08:32
1

std::async returns a future. Its destructor blocks if get or wait has not been called:

it may block if all of the following are true: the shared state was created by a call to std::async, the shared state is not yet ready, and this was the last reference to the shared state.

See std::futures from std::async aren't special! for a detailed treatment of the subject.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
1

Add these 2 lines at the end of main:

f1.wait();
f2.wait();

This will make sure the threads finish before main exists.

egur
  • 7,830
  • 2
  • 27
  • 47