
2011/4/23 Eric Blake <eblake@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