C++ Logo

std-discussion

Advanced search

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

From: Jens Maurer <jens.maurer_at_[hidden]>
Date: Wed, 30 Oct 2024 23:01:01 +0100
A few notes here:

"Sequenced before is an asymmetric, transitive, pair-wise relation between evaluations executed by a single thread ([intro.multithread])"
[intro.execution] p8

So, "sequenced before" is simply not defined between evaluations on different threads.

Your example expressions are covered by [intro.execution] p11
as long as co_await doesn't switch threads.

If co_await does switch threads, the library that implements such a co_await
should tell you which thread synchronization is applied.

At which point we should have a "happens-before" relation established
between evaluations on the "old" and on the "new" thread.
(Note that sequenced-before implies happens-before.)

I appreciate that the wording we have in [dcl.init.list]
claims that in the following example

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

"co_await expr1" is sequenced before "co_await expr2", even
though those might be executed on different threads.

Jens



On 30/10/2024 20.40, Артём Колпаков via Std-Discussion wrote:
> 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 22:01:07