1

I wonder if this code is fine or not:

#include <iostream>
#include <future>
struct Foo
{
    Foo()
        :m_a(0)
    {
    }

    int m_a;
};

int main()
{
    Foo f;
    auto handle =
        std::async( std::launch::async, 
                [](Foo* f) { std::cout << f->m_a << '\n'; } ,
                &f
              );

    handle.get();
}

I believe m_a should be protected by a synchronization mechanism but my colleague says it is not necessary.

EDIT: To clarify my question: I am worried that the STORE operation from the constructor of Foo() happens after the LOAD operation from the other thread. I can't see what mechanism prevents the compiler from executing those instruction in this order.

EDIT: I believe an enthusiastic compiler could decide to inline the constructor, and delay the STORE operation for after the CALL operation to std::async. In which case the second thread could access m_a before it has been committed to memory.

qdii
  • 12,505
  • 10
  • 59
  • 116
  • 2
    Nothing else is writing to `f` or reading from it in `main`, so there's nothing to synchronise. But it would help if you listed some of the arguments on each side. – juanchopanza Jan 26 '15 at 10:36
  • @juanchopanza There is something else writing to `f`: it's destructed automatically, i.e. the access to `f->m_a` should better not happen after `f` was destroyed. – Frerich Raabe Jan 26 '15 at 10:43
  • @FrerichRaabe It is destroyed after the call to `handle.get()`. So I meant there's no read-write in `main()` while the async operation is being performed. Now, it this wasn't safe, `std::async` would be pretty damn useless. – juanchopanza Jan 26 '15 at 10:49
  • 1
    I'll bet someone will find the relevant section of the standard and it will say that it is guaranteed that any thread created by `std::async` can see any changes made by the thread that invoked it prior to that invocation. – David Schwartz Jan 26 '15 at 10:51

1 Answers1

1

Yes, this is correctly synchronised.

From the specification for async, C++11 30.6.8/5:

the invocation of async synchronizes with the invocation of f.

where f is the function argument to async (the lambda in your example).

Initialisation of f.m_a is sequenced before the call to async, and therefore before any access by the asynchronous function.

Furthermore,

the completion of the function f is sequenced before the shared state is made ready.

so the access must happen before the call to get() returns, and therefore before the object is destroyed.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644