- Thread-safe initialization of static objects - 25 Updates
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:
Post a Comment