C++ Logo

std-discussion

Advanced search

What exactly does sequenced before mean and is it broken at all?

From: Артём Колпаков <ddvamp007_at_[hidden]>
Date: Wed, 30 Oct 2024 22:40:25 +0300
Often, when the definition of sequenced before is explained, it can be
called "program order" and it can be said that this is an ordering between
instructions in the source code, rather than an ordering in a single
thread. In general, you can think this way as long as you work in a
multithreaded environment with "normal" functions. However, when suspended
functions come into play, such as C++20 coroutines, such a model becomes
incorrect

The standard in [intro.execution] defines sequenced before, and also
defines in various places which evaluations are ordered in this way. By
definition, this is the relation between the evaluations of expressions in
the single thread

Let's look at some examples. There is no question what happens when we
resume the coroutine. From the caller's point of view, it looks like a
regular function call. But what can be said about the case of the
suspension of coroutine? Are the expressions in front of the suspension
point ordered with the expressions evaluated further In the thread? Next,
what happens inside the coroutine when it suspended and resumed on another
thread? Is it possible to say that there is a sequenced before between the
expressions before and after suspension? On the one hand, no, because the
threads are different, but on the other hand, yes, because sequenced before
is set between full expressions and is transitive. And if so, does it turn
out that there are two paths for different sequenced before at the same
time: from the caller and from behind the suspension point? Now imagine
that there are two suspension points inside the coroutine, passing through
which the coroutine changed the thread twice, to another and again to the
original one. In this case, given that there is only one thread, and that
it resumes twice in the coroutine, are the expressions before the first
suspension and the expressions after the second suspension ordered
sequenced before? Wouldn't there be two paths for the same sequenced before
at the same time, through the code and through the thread?

The standard says some things about evaluation co_await (see CWG 2466),
which, however, does not seem to me to help answer the questions posed. But
what about the case of other suspended functions, such as fibers and
coroutines from Boost and other places. The standard doesn't know anything
about them at all

You can approach it from the other side and assume that all evaluations
performed in different threads are not candidates for use in the sequenced
before expression. Then in what relation are they, unsequenced? But such
evaluations may overlap. Does this mean that the concept of sequenced
refers to runtime and only for evaluations in a single thread? It seems
that this may not bother us in principle, since when transferring the
coroutine to another thread, we must ensure synchronized with, that is,
inter-thread happens before. But sequenced before ordering is not only for
full expressions. Everything becomes extremely confusing if you imagine
such constructions

auto arr[] = {co_await expr1, co_await expr2, co_await expr3};

(co_await expr)(expr-list);

(co_await expr1)[expr2];

auto arr2[] = {(co_await expr1)(), (co_await expr2)[expr3]};

auto arr3[] = {fiber.await(expr1), fiber.await(expr2)};

If sequenced before ordering here loses its force due to thread switching,
then how can we think about such expressions? And synchronized with is
completely useless here...

With all that said, the understanding of sequenced before and sequenced as
a whole becomes distant and ghostly, which makes it extremely uncomfortable

Please help me figure this out

P.S. I would be glad if someone could tell me how to reply to messages in
this list via gmail

Regards, Artyom Kolpakov

Received on 2024-10-30 19:40:44