
Andrey Semashev wrote:
Hello Robert,
Wednesday, August 22, 2007, 12:43:03 AM, you wrote:
Tobias Schwinger wrote:
AFAICT, our "lightweight toolbox" is still insufficient to implement a thread-safe Singleton - I might be missing something, though. How would you initialize 'lightweight_mutex' when you can't know that ctors are run in static context (as within a shared library)?
Maybe it's possible to make 'detail::atomic_count' an aggregate and provide a macro for initialization (just as pthread does for its synchronization primitives). Then it would be trivial to implement a 'lightweight_once' on top of it...
Please start thinking about this. I don't see why this can't be a header only library. Of course in my case, the expanded headers (instantiated code) will be compiled into the serializatoin library as an implementation detail. I would much prefer that to having to link in another library.
Just came across this thread. I had a need of lightweight_call_once in my Boost.FSM library and implemented it. It is not implemented as an internal part of the library, but rather as a common tool, like lightweight_mutex.
Something you'd like to brush up as a Boost X-File ;-)? See http://article.gmane.org/gmane.comp.lib.boost.devel/162951
It can be found here:
Thanks! It's great you actually wrote a reusable tool. I finally found some time to review your code. Here are my (hopefully not too discouraging) comments: The pthreads implementation seems to be using a global Mutex, which is inefficient, because it causes concurrent initializations (that might have nothing to do with each other) to be queued. To make things worse, that Mutex is initialized with 'pthread_once'. Also, some platforms will not call 'mutex_destroyer' within a dynamic library (you probably know)... The "trigger" could contain the mutex and the macro for initialization would contain PTHREAD_MUTEX_INITIALIZER, so its creation can be done at compile time by setting up the appropriate bytes in the data segment (interestingly, you use a similar technique for the "no atomics variant"). Other implementations use "while (check) sleep; stuff", which seems sorta awkward to me. Can't we use "proper" synchronization? Win32 provides the 'InitOnceExecuteOnce' which seems to do pretty much all we need (it even takes a parameter to get a the state in and boost::function and a downcast from 'PVOID' will do for the type erasure). After putting another guard around it (to avoid the dynamic call and the construction of the boost::function) we're all done. I don't know too much about other threading platforms, but I'm sure there are similar means. We could also use a counter for the guard to use a Semaphore (if it's more handy to do so for some platform) to notify threads waiting for initialization to complete: if (is_init(trigger)) return; if (atomic_inc(cnt) > 1) // <-- gate point { // go to sleep, unless we missed initialization has finished if (! is_init(trigger)) sem.down(); atomic_dec(cnt); } else { client_func(); // make sure no further threads enter the gate (threads that // miss the change and enter anyway will leave again) make_called(trigger); // continue all threads after the gate for (int n; !! (n = cnt - 1) ;) { sem.up(n); yield(); // <-- might or might not be needed } // postcondition: sem >= 0 } For some platforms (such as x86) memory access is atomic, so atomic operations are just a waste of time for simple read/write operations as the 'is_init' and 'set_called' stuff. There's some code that throws exceptions with pretty, formatted error messages: So we're out of resources and execute a whole bunch of code to format an error message... That code might run into the same problem we're trying to report, so probably throwing something lightweight (such as an enum) is a more appropriate choice (and also gets us rid of some header dependencies). Regards, Tobias