4
votes
boost::shared_ptr<A> a = boost::shared_ptr<A>(new A);
a->i = 2;
strand.post([a](){assert(a->i == 2)});

or

io_service.post([a](){assert(a->i == 2)});

When I post a handler to strand or io_service in thread1, does the thread2 which execute handler see data changes before the post?

Java has similar thing Executor which make happen-before relationship:

Actions in a thread prior to the submission of a Runnable to an Executor happen-before its execution begins. Similarly for Callables submitted to an ExecutorService.

What about asio?

Notice that this question does not ask the execute order of handlers which been post to io_service or strand. Asio doc says if using strand, post(a) happen-before post(b). But this question ask: Does actions before(coding order) post(a) happen-before post(a)(handler a's execute)? This is about memory model. Let me clarify:

// in thread1
global variable a = 111;
start a new thread2 which will access global variable a

We know actions in thread1 before start thread2 happen-before thread2 start. That means: 1, final execute order is same as code order(compiler or CPU will not change the order). 2, global variable a value set action will flush to main memory (or sync to thread2's CPU cache), then thread2 will updated to newest value of global variable a (of course, till it start).

asio's post is similar with this example, so I ask this question. I think asio's post should make happen-before relationship, or else it will be useless. But I am not sure, and I want to know how asio makes it.

To make happen-before, there is 3 way: 1, thread start, join. 2, lock and unlock a mutex. 3, memory barrier.

1
Lets clearify: you think its possible a->i = 2; will execute after strand.post ? - PSIAlt
Yes. But there is other situation: the execute order is same as code order, but thread2 can not read the newest value set of a->i. If there is no happen-before relationship, both of them can happen - jean
Note that if you use the strand correctly, the post happens "from the same strand" so the whole flow will be as-if single-threaded sequential - sehe
@sehe I don't think this is a dup. The question you reference has to do with the ordering of function invocations, but this question has to do with the ordering of memory accesses. The answer will depend on the memory model and whether post() has implicit release semantics and whether the functor invocation has implicit acquire semantics. My guess is yes, but I don't know if that is stated anywhere; it may only be a side effect of the internal thread-safe queue io_service contains. - rhashimoto

1 Answers

1
votes

All of the following have a happens-before relation on memory ordering: io_service::post(), io_service::dispatch(), strand::post(), and strand::dispatch(). This is primarily the the result of io_service and strand providing a strong guarantee that a single object may be used concurrently. In this case where potential blocking synchronization is not required, such as when a handler posted to dispatch() may be executed within the context of the dispatch() function, then a full memory fence is issued.

The documentation notes the use of memory barriers for handler allocation:

[...] The implementation will insert appropriate memory barriers to ensure correct memory visibility should allocation functions need to be called from different threads.