On 9/7/2023 2:55 AM, Paavo Helde wrote:
> 06.09.2023 21:15 Bonita Montero kirjutas:
>> With C++11 static local or global data is initialized thread-safe.
>> This is usually done with double checked locking (DCL). DCL needs
>> a flag along with the static data which shows if the data already
>> is initialized and a mutex which guards the initalization.
>> I assumed that an implementation simply would use a central mutex
>> for all static data objects since it includes a kernel semaphore,
>> which is a costly resource.
>> To find out if this is true I wrote the below application:
> [...]
>> SleepAtInitialize<IObj> is instantiated only once per thread with this
>> code. So the threads don't share a central object. If there would be
>> a central mutex used the above code would run for about 10s. But the
>> code does run about one second,
>
> There is a note in the standard: "[Note: This definition permits
> initialization of a sequence of ordered variables concurrently with
> another sequence. —end note]"
>
> i.e. there are indivual mutexes per
>> each instantiation of SleepAtInitiaize<>. I.e. the creation of the
>> mutex is also done with the static initialization. The mutex constructor
>> is noexcept, so mutexes always must be created on their first use. This
>> is done while the DCL-locked creation of the static object. So at last
>> static initialization should be declared to throw a system_errror. But
>> I can't find anything about that in the standard.
>
> Some debugging with VS2022 seems to indicate it is using a Windows
> critical section for thread-safe statics initialization.
> EnterCriticalSection() does not return any error code and of course does
> not throw any C++ exceptions either, so it is supposed to never fail.
If it does fail, then some rather radical shit has hit the fan.
>
> Yes, it's true it can throw a Windows structured exception
> EXCEPTION_POSSIBLE_DEADLOCK (after 30 days by default). But this would
> be considered as a fault in the program. This is what the C++ standard
> says about deadlocks (again a footnote):
>
> "The implementation must not introduce any deadlock around execution of
> the initializer. Deadlocks might still be caused by the program logic;
> the implementation need only avoid deadlocks due to its own
> synchronization operations."
>
> So I gather that in case the thread-safe static init synchronization
> fails, there must be a bug in the implementation. No C++ exceptions
> would be thrown anyway.
Right. The implementation must get it right using fine grain locking,
amortized table locking, or some other means. The compiler and POSIX
worth together like a system. Iirc, I saw another way that was
lock-free, but allowed a thread to create an object, then might have to
delete it because it was not the first one to be installed in the sense
of being visible to other threads. This was decades ago, iirc it used
CAS. No mutex.