Date: Wed, 30 Oct 2024 17:49:42 -0400
On Wed, Oct 30, 2024 at 3:40 PM Артём Колпаков via Std-Discussion
<std-discussion_at_[hidden]> 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
By which definition? You cited a vernacular general understanding
"definition" as "program order". But that is *not* how the standard
actually defines it. As far as the standard is concerned:
> An expression X is said to be sequenced before an expression Y if every value computation and every side effect associated with the expression X is sequenced before every value computation and every side effect associated with the expression Y.
So that's what we have to go with. There is more to this, as
particular expressions define the "sequenced before" relationship
between other components of the expression.
> 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?
This is part of the misunderstanding here: the concept of expressions
being "sequenced before" another does not care about threads. It does
not matter what thread one expression is evaluated on; if there is a
"sequenced before" relationship between the two expressions, then
those rules apply.
Coroutine suspension happens as part of the evaluation of an
`co_await` or similar expression. So with regard to full expressions
before or after the `co_await`, the "sequenced before" status is
clear. A notation in the standard says, "With respect to sequencing,
an await-expression is indivisible."
The description of the behavior of coroutine suspension strongly
suggests a "sequenced before" relationship between the expression and
what the result of that expression does with regard to suspensions and
the like. It says that the await-expression is evaluated "and then"
other things happen. I'm happy saying that this counts as being
"sequenced before" those things.
> 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?
Irrelevant. Again, the above rules apply.
> 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};
Every expression within a braced-init-list has a clear "sequenced
before" relationship.
>
> (co_await expr)(expr-list);
That looks like a function call. From the standard:
> The postfix-expression is sequenced before each expression in the expression-list and any default argument.
So the `co_await expr` happens first.
> (co_await expr1)[expr2];
Again, from the standard:
> The expression E1 is sequenced before the expression E2.
Where `E1` is the `co_await expr1`.
> auto arr2[] = {(co_await expr1)(), (co_await expr2)[expr3]};
Same as above, just nested.
> auto arr3[] = {fiber.await(expr1), fiber.await(expr2)};
Same as above, just nested.
> 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
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
<std-discussion_at_[hidden]> 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
By which definition? You cited a vernacular general understanding
"definition" as "program order". But that is *not* how the standard
actually defines it. As far as the standard is concerned:
> An expression X is said to be sequenced before an expression Y if every value computation and every side effect associated with the expression X is sequenced before every value computation and every side effect associated with the expression Y.
So that's what we have to go with. There is more to this, as
particular expressions define the "sequenced before" relationship
between other components of the expression.
> 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?
This is part of the misunderstanding here: the concept of expressions
being "sequenced before" another does not care about threads. It does
not matter what thread one expression is evaluated on; if there is a
"sequenced before" relationship between the two expressions, then
those rules apply.
Coroutine suspension happens as part of the evaluation of an
`co_await` or similar expression. So with regard to full expressions
before or after the `co_await`, the "sequenced before" status is
clear. A notation in the standard says, "With respect to sequencing,
an await-expression is indivisible."
The description of the behavior of coroutine suspension strongly
suggests a "sequenced before" relationship between the expression and
what the result of that expression does with regard to suspensions and
the like. It says that the await-expression is evaluated "and then"
other things happen. I'm happy saying that this counts as being
"sequenced before" those things.
> 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?
Irrelevant. Again, the above rules apply.
> 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};
Every expression within a braced-init-list has a clear "sequenced
before" relationship.
>
> (co_await expr)(expr-list);
That looks like a function call. From the standard:
> The postfix-expression is sequenced before each expression in the expression-list and any default argument.
So the `co_await expr` happens first.
> (co_await expr1)[expr2];
Again, from the standard:
> The expression E1 is sequenced before the expression E2.
Where `E1` is the `co_await expr1`.
> auto arr2[] = {(co_await expr1)(), (co_await expr2)[expr3]};
Same as above, just nested.
> auto arr3[] = {fiber.await(expr1), fiber.await(expr2)};
Same as above, just nested.
> 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
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
Received on 2024-10-30 21:49:56