2011/4/23 Eric Blake <eblake(a)redhat.com>:
mingw lacks the counterpart to PTHREAD_MUTEX_INITIALIZER, so the
best we can do is portably expose once-only runtime initialization.
* src/util/threads.h (virOnceControlPtr): New opaque type.
(virOnceFunc): New callback type.
(virOnce): New prototype.
* src/util/threads-pthread.h (virOnceControl): Declare.
(VIR_ONCE_CONTROL_INITIALIZER): Define.
* src/util/threads-win32.h (virOnceControl)
(VIR_ONCE_CONTROL_INITIALIZER): Likewise.
* src/util/threads-pthread.c (virOnce): Implement in pthreads.
* src/util/threads-win32.c (virOnce): Implement in WIN32.
* src/libvirt_private.syms: Export it.
---
v2: change WIN32 implementation to use lower-level primitives available
to mingw and older windows, rather than higher level but newer INIT_ONCE.
Meanwhile, I noticed that gnulib has an LGPLv2+ 'lock' module that
provides a lot of multi-threading primitives for both pthreads, pth,
and mingw; maybe we should think about rewriting threads.c in terms
of gnulib and only have one implementation, rather than maintaining
pthread and mingw in parallel?
+int virOnce(virOnceControlPtr once, virOnceFunc func)
+{
+ if (!once->complete) {
+ if (InterlockedIncrement(&once->init) == 1) {
+ /* We're the first thread. */
+ func();
+ once->complete = 1;
+ } else {
+ /* We're a later thread. Decrement the init counter back
+ * to avoid overflow, then yield until the first thread
+ * marks that the function is complete. It is rare that
+ * multiple threads will be waiting here, and since each
+ * thread is yielding except the first, we should get out
+ * soon enough. */
+ InterlockedDecrement(&once->init);
+ while (!once->complete)
+ Sleep(0);
+ }
+ }
+ return 0;
+}
Compared to the gnulib lock module version, this one is less complex
and I don't get why gnulib uses 3 states for the init and complete
variables and an additional lock object. The only reason could be to
minimize the time busy waiting in the while loop.
+
+struct virOnceControl {
+ long init; /* 0 at startup, > 0 if init has started */
+ volatile long complete; /* 0 until first thread completes callback */
+};
MSDN docs about InterlockedIncrement suggest that init should be volatile too.
ACK, with init marked volatile.
Matthias