Date: Thu, 1 Jun 2023 12:26:19 -0400
On Thu, Jun 1, 2023 at 11:04 AM Phil Bouchard <boost_at_[hidden]> wrote:
>
>
>
> On 6/1/23 10:42, Jason McKesson via Std-Proposals wrote:
>
> >>> Are you suggesting that the compiler analyse the if-find line, find all lockable
> >>> elements in use there, then lock them behind the scenes without user action
> >>> and keep them locked until the end of the scope?
> >>
> >> Yes that's what I am trying to say since the beginning.
> >
> > That is not possible in general. Some of that stuff can be hidden
> > behind function calls that are opaque to the compiler. It does not
> > know what gets called in that code, and since the object is shared, it
> > may not even be obvious whether the opaque function call can access
> > it.
> >
> > Therefore, the compiler has two choices: don't automagically engage
> > the lock (and therefore potentially break code), or do automagically
> > engage the lock. But the latter can be pretty bad too; if the opaque
> > function doesn't use it, you now lock the container for an
> > indeterminate period of time. Especially since the opaque function can
> > have arbitrary amounts of code in it.
>
> Why would it be indeterminate assuming you have a
> "thread-scope-recursive" mutex?
I don't know what you mean by that.
What is "indeterminate" is whether the container *needs* to be locked
for the duration of the condition. If you need to manipulate the
container in the condition, then it needs to be locked. If you don't,
then it *should not* be locked.
And we're not talking about the cost of doing a mutex lock/unlock.
That body of code can be unknowably large. The idea that an
`if(container.empty()) {...}` would just automatically, always, and
*invisibly* lock the container for the entire duration of `...` is
horrifying. You have no idea what is going on in that code or how long
it will take. It might decide to wait on the completion of some other
task, one which doesn't use the container at all.
Invisibly locking mutexes for arbitrary lengths of time when that is
not strictly needed by the code in question is a bug. Even if it
doesn't cause some kind of deadlock at some point (which BTW is
*another* problem here; you can't lock two containers and manipulate
them as a unit), it can cause a lot of problems. It becomes very easy
to take working code and turn it into broken code by inserting
something seemingly innocuous.
Which again was supposed to be the point of "fixing" this.
> >> Here is just an
> >> example of the net code the compiler would see after making temporary
> >> variables last for the duration of the conditional scope:
> >> https://github.com/philippeb8/std__ts/blob/master/ts.cpp#L23
> >>
> >>>> Again, where's the problem if both containers feature a recursive mutex?
> >>>
> >>> Recursion is not the problem. Atomicity of the action is. The point I tried to
> >>> make with the previous example is that one must reason about the duration of
> >>> the lock to decide when it must start and when it must end, so the conditions
> >>> don't change between statements. This is also the analogy of the transaction
> >>> that someone else posted in this thread.
> >>>
> >>> With the simple examples we're discussing here, it might be obvious what to do
> >>> and thus make solutions obvious. What others and I are telling you is that
> >>> when it gets to really complex thread-safe code, you *have* to reason about
> >>> when locks must start and when they must end, and what other locks you have.
> >>> Plus, reason about the order of locks, to avoid deadlocks.
> >>>
> >>> Thread-safety requires having the smallest possible critical sections, but no
> >>> smaller. If you pulverise your locks, you add overhead and actually lose
> >>> safety.
> >>>
> >>>> Regarding teaching, this is higher-level programming so a new namespace
> >>>> should encompass these new classes.
> >>>
> >>> That's not what I meant. You're oversimplifying the answer to a complex
> >>> question. Refer back to the top of this email:
> >>>
> >>> if (!container.empty())
> >>> container.push)back(1);
> >>>
> >>> This may have no data race and thus cause no data corruption, but it's not
> >>> what was required because the states may have changed. If this is still
> >>> allowed, then just using the container in question does *not* confer thread-
> >>> safety. And therefore, if it is allowed to compile, how do you propose we
> >>> teach everyone *how* to write code to actually make it thread-safe?
> >>
> >> You add some type trait allowing the compiler to determine whether the
> >> class is a "thread-safe" class or not.
> >
> > This is what I like to call "bulldozer design". You declare that you
> > want X, and when people point out problems with achieving X, you take
> > a bulldozer to whatever those problems are. Every time someone points
> > out a problem, you say "oh, we'll add a niche solution to fix that."
>
> What you call "bulldozer design" I call "intelligently abstracted
> innovations".
Whatever name you want to give it, it still leads to a lot of cruft.
> Given you guys are the smartest C++ developers in the world, I would
> have expected a more positive feedback.
Or maybe you're not as correct as you think you are about the
importance of such containers.
I noticed that you didn't even respond to my point about the lack of
"thread-safe" containers in major C++ libraries. You make these wild
factual assertions about the importance of what you think is good, but
when someone tells you to put actual facts behind them (or shows facts
to the contrary), you tend to just ignore it as if it were never said.
Maybe eventually you acknowledge them, but you continue to push for
the thing as if a central pillar of what you were proposing didn't
have its reasons for existence kicked out from under it.
If you want "more positive feedback", you should strive not to come to
a fact-fight unarmed.
>
>
>
> On 6/1/23 10:42, Jason McKesson via Std-Proposals wrote:
>
> >>> Are you suggesting that the compiler analyse the if-find line, find all lockable
> >>> elements in use there, then lock them behind the scenes without user action
> >>> and keep them locked until the end of the scope?
> >>
> >> Yes that's what I am trying to say since the beginning.
> >
> > That is not possible in general. Some of that stuff can be hidden
> > behind function calls that are opaque to the compiler. It does not
> > know what gets called in that code, and since the object is shared, it
> > may not even be obvious whether the opaque function call can access
> > it.
> >
> > Therefore, the compiler has two choices: don't automagically engage
> > the lock (and therefore potentially break code), or do automagically
> > engage the lock. But the latter can be pretty bad too; if the opaque
> > function doesn't use it, you now lock the container for an
> > indeterminate period of time. Especially since the opaque function can
> > have arbitrary amounts of code in it.
>
> Why would it be indeterminate assuming you have a
> "thread-scope-recursive" mutex?
I don't know what you mean by that.
What is "indeterminate" is whether the container *needs* to be locked
for the duration of the condition. If you need to manipulate the
container in the condition, then it needs to be locked. If you don't,
then it *should not* be locked.
And we're not talking about the cost of doing a mutex lock/unlock.
That body of code can be unknowably large. The idea that an
`if(container.empty()) {...}` would just automatically, always, and
*invisibly* lock the container for the entire duration of `...` is
horrifying. You have no idea what is going on in that code or how long
it will take. It might decide to wait on the completion of some other
task, one which doesn't use the container at all.
Invisibly locking mutexes for arbitrary lengths of time when that is
not strictly needed by the code in question is a bug. Even if it
doesn't cause some kind of deadlock at some point (which BTW is
*another* problem here; you can't lock two containers and manipulate
them as a unit), it can cause a lot of problems. It becomes very easy
to take working code and turn it into broken code by inserting
something seemingly innocuous.
Which again was supposed to be the point of "fixing" this.
> >> Here is just an
> >> example of the net code the compiler would see after making temporary
> >> variables last for the duration of the conditional scope:
> >> https://github.com/philippeb8/std__ts/blob/master/ts.cpp#L23
> >>
> >>>> Again, where's the problem if both containers feature a recursive mutex?
> >>>
> >>> Recursion is not the problem. Atomicity of the action is. The point I tried to
> >>> make with the previous example is that one must reason about the duration of
> >>> the lock to decide when it must start and when it must end, so the conditions
> >>> don't change between statements. This is also the analogy of the transaction
> >>> that someone else posted in this thread.
> >>>
> >>> With the simple examples we're discussing here, it might be obvious what to do
> >>> and thus make solutions obvious. What others and I are telling you is that
> >>> when it gets to really complex thread-safe code, you *have* to reason about
> >>> when locks must start and when they must end, and what other locks you have.
> >>> Plus, reason about the order of locks, to avoid deadlocks.
> >>>
> >>> Thread-safety requires having the smallest possible critical sections, but no
> >>> smaller. If you pulverise your locks, you add overhead and actually lose
> >>> safety.
> >>>
> >>>> Regarding teaching, this is higher-level programming so a new namespace
> >>>> should encompass these new classes.
> >>>
> >>> That's not what I meant. You're oversimplifying the answer to a complex
> >>> question. Refer back to the top of this email:
> >>>
> >>> if (!container.empty())
> >>> container.push)back(1);
> >>>
> >>> This may have no data race and thus cause no data corruption, but it's not
> >>> what was required because the states may have changed. If this is still
> >>> allowed, then just using the container in question does *not* confer thread-
> >>> safety. And therefore, if it is allowed to compile, how do you propose we
> >>> teach everyone *how* to write code to actually make it thread-safe?
> >>
> >> You add some type trait allowing the compiler to determine whether the
> >> class is a "thread-safe" class or not.
> >
> > This is what I like to call "bulldozer design". You declare that you
> > want X, and when people point out problems with achieving X, you take
> > a bulldozer to whatever those problems are. Every time someone points
> > out a problem, you say "oh, we'll add a niche solution to fix that."
>
> What you call "bulldozer design" I call "intelligently abstracted
> innovations".
Whatever name you want to give it, it still leads to a lot of cruft.
> Given you guys are the smartest C++ developers in the world, I would
> have expected a more positive feedback.
Or maybe you're not as correct as you think you are about the
importance of such containers.
I noticed that you didn't even respond to my point about the lack of
"thread-safe" containers in major C++ libraries. You make these wild
factual assertions about the importance of what you think is good, but
when someone tells you to put actual facts behind them (or shows facts
to the contrary), you tend to just ignore it as if it were never said.
Maybe eventually you acknowledge them, but you continue to push for
the thing as if a central pillar of what you were proposing didn't
have its reasons for existence kicked out from under it.
If you want "more positive feedback", you should strive not to come to
a fact-fight unarmed.
Received on 2023-06-01 16:26:32