Tuesday, September 19, 2023

Digest for comp.lang.c++@googlegroups.com - 25 updates in 1 topic

Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Sep 18 08:11PM -0400

Chris M. Thomasson wrote:
 
> Yeah. It's up to us programmers to make sure everything is right on our
> end. What about:
 
> intra_process_mutex_recursive m_mutex_recurive;
 
Agree, good names are important for readability.
 
The issue with POSIX mutex is that there is only one C type for all
mutex types, pthread_mutex_t so at least one dispatch is necessary (and,
at least in the current Linux implementation, many dispatches are done).
 
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Sep 18 08:15PM -0400

Bonita Montero wrote:
>> debugging, ...
 
> The constructor is noexcept, so the kernel-part of the mutex for the
> slow path needs to be created deferred.
Or, when there is nothing to create, there is nothing to defer,
 
> This of course can fail
and, of course, nothing can fail.
 
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Sep 18 08:30PM -0400

Bonita Montero wrote:
 
> Call it mutex or whatever: the standard requires that contenders
> sleep in place, and that's only possible with something that works
> like a mutex.
Define "works like a mutex".
Richard Damon <Richard@Damon-Family.org>: Sep 18 09:04PM -0400

On 9/18/23 8:37 AM, Bonita Montero wrote:
 
>> Why?
 
> Because a mutex does this also with an appropriate exception. And there
> wouldn't be more problems if such an excepton would be thrown, but less.
 
So implementions needs to be allowed to do the wrong thing?
 
Shows your sense of how programs shoudld (not) work.
 
 
> I've shown that libc++, libstdc++ and MSVC handle this with per-object
> mutexes, and if you create an arbitrary number of mutexes this might
> fail.
 
Nope. You show that you can not read.
 
Others have pointed out what the code of those functions ACTUALLY do, an
it isn't what you are calling a "Mutex", and it can't fail.
Bonita Montero <Bonita.Montero@gmail.com>: Sep 19 05:08AM +0200

Am 18.09.2023 um 21:21 schrieb Chris M. Thomasson:
 
>>> I still don't think you know how they work.
 
>> You didn't read the paper, not me.
 
> I already know how futexes work. In and out.
 
You said that everythong happens in userspace. And you didn't notice
that it isn't impossible to have a blocking thread this way ...
Bonita Montero <Bonita.Montero@gmail.com>: Sep 19 05:10AM +0200

Am 19.09.2023 um 02:15 schrieb Pavel:
 
> Or, when there is nothing to create, there is nothing to defer,
 
I've shown that static initialization actually uses a mutex per object.
The runtimes for sure not pre-create a number of mutexes for that since
static objects are just zeroed memory before initialization.
Bonita Montero <Bonita.Montero@gmail.com>: Sep 19 05:11AM +0200

Am 18.09.2023 um 21:22 schrieb Chris M. Thomasson:
 
>> With the deferred initialization of a C++ mutex the mutex
>> could fail on synchronization even without being bad.
 
> The impl must make sure to get it right wrt static initialization.
 
Idiot - I wasn't talking about static initialization at this point.
Bonita Montero <Bonita.Montero@gmail.com>: Sep 19 05:14AM +0200

Am 19.09.2023 um 03:04 schrieb Richard Damon:
 
> So implementions needs to be allowed to do the wrong thing?
 
It would be the right thing if this would be specified.
 
> Others have pointed out what the code of those functions ACTUALLY do,
> an it isn't what you are calling a "Mutex", and it can't fail.
 
You're as stupid as Chris - I wasn't talking about whether the code
can fail or not at this point, but just that there's actually a mutex
per static object.
Bonita Montero <Bonita.Montero@gmail.com>: Sep 19 05:16AM +0200

Am 18.09.2023 um 21:42 schrieb Kaz Kylheku:
 
> static_thing o(A);
> }
> ...
 
That doesn't make sense in the context since we talked about
a deadlock by the mutex included in static initialization and
not by mutexes you handle separarely - idiot.
Bonita Montero <Bonita.Montero@gmail.com>: Sep 19 05:17AM +0200

Am 19.09.2023 um 02:30 schrieb Pavel:
 
>> sleep in place, and that's only possible with something that works
>> like a mutex.
 
> Define "works like a mutex".
 
A lock which makes a thread to sleep on contention.
Kaz Kylheku <864-117-4973@kylheku.com>: Sep 19 04:05AM


> That doesn't make sense in the context since we talked about
> a deadlock by the mutex included in static initialization and
> not by mutexes you handle separarely - idiot.
 
The subject of the thread is "Thread-safe initialization of static
objects". This is what I'm writing about. I have static objects
above (which don't have mutexes, or any data members at all). I'm
assuming their initialization is made thread-safe via (hidden, not
explicitly visible) mutexes generated by the compiler.
 
You /seemed/ to be claiming that such an arrangement cannot deadlock.
 
Whether or not, I'm showing above how it can.
 
1. One static initialization can be nested inside another,
such that the calling thread momentarily owns both mutexes.
 
2. Two or more threads can execute initializations.
 
3. Thus, two threads can nest the same pair of initializations
in opposite order.
 
(Therefore, I agree with you; those hidden mutexes could run into
a situation which their lock operations could, in principle, detect
and turn into an exception.)
 
If you didn't claim that static initialization with hidden mutexes
cannot deadlock, please disregard this as a counterexample to anything
you said, but it still holds on its own.
 
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @Kazinator@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 10:01PM -0700

On 9/18/2023 8:08 PM, Bonita Montero wrote:
 
>> I already know how futexes work. In and out.
 
> You said that everythong happens in userspace. And you didn't notice
> that it isn't impossible to have a blocking thread this way ...
 
We can choose to use the kernel to wait, if we choose to. You are
bringing back memories... Thanks for that Bonita. :^)
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 10:01PM -0700

On 9/18/2023 8:10 PM, Bonita Montero wrote:
> Am 19.09.2023 um 02:15 schrieb Pavel:
 
>> Or, when there is nothing to create, there is nothing to defer,
 
> I've shown that static initialization actually uses a mutex per object.
 
Really?
 
 
 
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 10:02PM -0700

On 9/18/2023 8:11 PM, Bonita Montero wrote:
>>> could fail on synchronization even without being bad.
 
>> The impl must make sure to get it right wrt static initialization.
 
> Idiot - I wasn't talking about static initialization at this point.
 
Oh, I think you are. Are you now talking about how many mutexes can a
process create before it dies?
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 10:06PM -0700

On 9/18/2023 8:14 PM, Bonita Montero wrote:
>> an it isn't what you are calling a "Mutex", and it can't fail.
 
> You're as stupid as Chris - I wasn't talking about whether the code
> can fail or not at this point,
 
Altering goal posts now, oh wonderful one?
 
 
 
> but just that there's actually a mutex
> per static object.
 
Really? Humm...
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 10:08PM -0700

On 9/18/2023 9:05 PM, Kaz Kylheku wrote:
> assuming their initialization is made thread-safe via (hidden, not
> explicitly visible) mutexes generated by the compiler.
 
> You /seemed/ to be claiming that such an arrangement cannot deadlock.
 
If the impl can deadlock wrt satic init, then we might as well take an
infinite walk off a very short peer.
 
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 10:11PM -0700

On 9/18/2023 10:08 PM, Chris M. Thomasson wrote:
 
>> If you didn't claim that static initialization with hidden mutexes
>> cannot deadlock, please disregard this as a counterexample to anything
>> you said, but it still holds on its own.
 
The impl better sure make god damn sure that there is no deadlock on
static init of say, POD's.
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 10:14PM -0700

On 9/18/2023 8:17 PM, Bonita Montero wrote:
>>> like a mutex.
 
>> Define "works like a mutex".
 
> A lock which makes a thread to sleep on contention.
 
I assume going to "sleep" means waiting in the kernel on a slow path.
Otherwise, humm... Are you talking about user logic that sleeps for a
second or two? Why should that matter at all? The impl shall arrange
things such that there is no deadlock. Let me guess Bonita, you think
that user logic that waits for infinity while its trying to initialize
itself should be handled in the std? Humm... Not sure about you.
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 10:16PM -0700

On 9/18/2023 8:08 PM, Bonita Montero wrote:
 
>>> You didn't read the paper, not me.
 
>> I already know how futexes work. In and out.
 
> You said that everythong happens in userspace.
 
Any user algorithm that needs to be able to wait on a predicate. Futex!
 
 
 
Kaz Kylheku <864-117-4973@kylheku.com>: Sep 19 05:50AM

> The impl better sure make god damn sure that there is no deadlock on
> static init of say, POD's.
 
PODs aren't a special category. Never mind that, how about scalars?
 
The problem is that the static initializer expressions in C++ can call
functions.
 
int B();
 
int A()
{
    static int a = B();
}
 
void B()
{
static int b = A();
}
 
What prevents a deadlock here, in mutex-guarded static initialization
logic, if one thread calls A at the same time as another calls B?
 
Here is one way a deadlock could be prevented, at least in a program
which doesn't mix its own explicit mutexes with static initializations:
just have one global mutex (a recursive one) for all lazy static
initializations.
 
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @Kazinator@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 11:19PM -0700

On 9/18/2023 10:50 PM, Kaz Kylheku wrote:
> which doesn't mix its own explicit mutexes with static initializations:
> just have one global mutex (a recursive one) for all lazy static
> initializations.
 
A allowing recursion would work, but I tend to try to avoid those suckers.
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 18 11:20PM -0700

On 9/18/2023 10:50 PM, Kaz Kylheku wrote:
> {
> static int b = A();
> }
 
Would that go into infinite recursion?
 
Kaz Kylheku <864-117-4973@kylheku.com>: Sep 19 07:59AM

>> static int b = A();
>> }
 
> Would that go into infinite recursion?
 
Firstly, I also wanted an int return on B() and return statements.
 
Yes; even in a single threaded situation, we have a recursion problem.
So that's the overriding problem, not whether we get deadlock with
multiple threads.
 
If the one and only thread calls A(), it will re-enter A() without
the initialization having completed which is bad.
 
Can you think of some static init scenario that works fine single
threaded, but potentially deadlocks when threads are present?
 
Even if not, be that as it may, the mutex deadlock detection could
debug the situation, whether just one thread triggers it, or
several.
 
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @Kazinator@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
Bonita Montero <Bonita.Montero@gmail.com>: Sep 19 10:43AM +0200

Am 19.09.2023 um 07:01 schrieb Chris M. Thomasson:
 
> We can choose to use the kernel to wait, if we choose to.
> You are bringing back memories... Thanks for that Bonita. :^)
 
The futex part of a mutex is for the slow path. It appears that
this is done with a kernel intervention since there's no explicit
kernel call.
Bonita Montero <Bonita.Montero@gmail.com>: Sep 19 10:46AM +0200

Am 19.09.2023 um 07:01 schrieb Chris M. Thomasson:
 
>> I've shown that static initialization actually uses a mutex per object.
 
> Really?
 
Here, a third time the code for you:
 
#include <iostream>
#include <utility>
#include <vector>
#include <thread>
 
using namespace std;
 
int main()
{
struct InitSleep
{
InitSleep()
{
this_thread::sleep_for( 1s );
}
};
constexpr unsigned N_THREADS = 100;
auto unroll = []<size_t ... Indices>( index_sequence<Indices ...>, auto
fn )
{
(fn.template operator ()<Indices>(), ...);
};
vector<jthread> threads;
threads.reserve( N_THREADS );
unroll( make_index_sequence<N_THREADS>(),
[&]<size_t T>()
{
threads.emplace_back( []( auto )
{
static InitSleep is;
}, integral_constant<size_t, T>() );
} );
}
 
The lambda's calling operator is instantiated N_THREADS time, thereby
having a static object per calling operator. If there would be a central
mutex for all static initializations the code would run about 100s, but
it actually finishes after one second.
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page.
To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com.

No comments: