diff --git a/glib/gthread.c b/glib/gthread.c index 6531f70d7..cb4a2e6fe 100644 --- a/glib/gthread.c +++ b/glib/gthread.c @@ -630,13 +630,25 @@ g_once_impl (GOnce *once, if (once->status != G_ONCE_STATUS_READY) { + gpointer retval; + once->status = G_ONCE_STATUS_PROGRESS; g_mutex_unlock (&g_once_mutex); - once->retval = func (arg); + retval = func (arg); g_mutex_lock (&g_once_mutex); +/* We prefer the new C11-style atomic extension of GCC if available. If not, + * fall back to always locking. */ +#if defined(G_ATOMIC_LOCK_FREE) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) && defined(__ATOMIC_SEQ_CST) + /* Only the second store needs to be atomic, as the two writes are related + * by a happens-before relationship here. */ + once->retval = retval; + __atomic_store_n (&once->status, G_ONCE_STATUS_READY, __ATOMIC_RELEASE); +#else + once->retval = retval; once->status = G_ONCE_STATUS_READY; +#endif g_cond_broadcast (&g_once_cond); } diff --git a/glib/gthread.h b/glib/gthread.h index 96f536916..a30815eb8 100644 --- a/glib/gthread.h +++ b/glib/gthread.h @@ -234,14 +234,23 @@ GLIB_AVAILABLE_IN_ALL void g_once_init_leave (volatile void *location, gsize result); -#ifdef G_ATOMIC_OP_MEMORY_BARRIER_NEEDED -# define g_once(once, func, arg) g_once_impl ((once), (func), (arg)) -#else /* !G_ATOMIC_OP_MEMORY_BARRIER_NEEDED*/ +/* Use C11-style atomic extensions to check the fast path for status=ready. If + * they are not available, fall back to using a mutex and condition variable in + * g_once_impl(). + * + * On the C11-style codepath, only the load of once->status needs to be atomic, + * as the writes to it and once->retval in g_once_impl() are related by a + * happens-before relation. Release-acquire semantics are defined such that any + * atomic/non-atomic write which happens-before a store/release is guaranteed to + * be seen by the load/acquire of the same atomic variable. */ +#if defined(G_ATOMIC_LOCK_FREE) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) && defined(__ATOMIC_SEQ_CST) # define g_once(once, func, arg) \ - (((once)->status == G_ONCE_STATUS_READY) ? \ + ((__atomic_load_n (&(once)->status, __ATOMIC_ACQUIRE) == G_ONCE_STATUS_READY) ? \ (once)->retval : \ g_once_impl ((once), (func), (arg))) -#endif /* G_ATOMIC_OP_MEMORY_BARRIER_NEEDED */ +#else +# define g_once(once, func, arg) g_once_impl ((once), (func), (arg)) +#endif #ifdef __GNUC__ # define g_once_init_enter(location) \