From: Matthias Clasen Date: Tue, 8 Jul 2003 23:43:48 +0000 (+0000) Subject: Support for one-time initialization functions. (#69668, Sebastian X-Git-Url: http://git.openbox.org/?a=commitdiff_plain;h=876f9078636e9543a3fec402f13a00dca2af9f41;p=dana%2Fcg-glib.git Support for one-time initialization functions. (#69668, Sebastian 2003-07-09 Matthias Clasen Support for one-time initialization functions. (#69668, Sebastian Wilhelmi) * configure.in: Check whether double checked locking is safe, define g_once() in glibconfig.h accordingly. * glib/gthread.h: Add GOnce, GOnceStatus, G_ONCE_INIT and g_once_impl. * glib/gthread.c (g_once_impl): Fallback implementation using a mutex if double checked locking is unsafe. * tests/thread-test.c: Add tests for g_once(). --- diff --git a/ChangeLog b/ChangeLog index 4be1507a..32a940ad 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2003-07-09 Matthias Clasen + + Support for one-time initialization functions. (#69668, Sebastian Wilhelmi) + + * configure.in: Check whether double checked locking is safe, define g_once() in + glibconfig.h accordingly. + * glib/gthread.h: Add GOnce, GOnceStatus, G_ONCE_INIT and g_once_impl. + * glib/gthread.c (g_once_impl): Fallback implementation using a mutex if double checked + locking is unsafe. + * tests/thread-test.c: Add tests for g_once(). + 2003-07-02 Matthias Clasen * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 4be1507a..32a940ad 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,14 @@ +2003-07-09 Matthias Clasen + + Support for one-time initialization functions. (#69668, Sebastian Wilhelmi) + + * configure.in: Check whether double checked locking is safe, define g_once() in + glibconfig.h accordingly. + * glib/gthread.h: Add GOnce, GOnceStatus, G_ONCE_INIT and g_once_impl. + * glib/gthread.c (g_once_impl): Fallback implementation using a mutex if double checked + locking is unsafe. + * tests/thread-test.c: Add tests for g_once(). + 2003-07-02 Matthias Clasen * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index 4be1507a..32a940ad 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,14 @@ +2003-07-09 Matthias Clasen + + Support for one-time initialization functions. (#69668, Sebastian Wilhelmi) + + * configure.in: Check whether double checked locking is safe, define g_once() in + glibconfig.h accordingly. + * glib/gthread.h: Add GOnce, GOnceStatus, G_ONCE_INIT and g_once_impl. + * glib/gthread.c (g_once_impl): Fallback implementation using a mutex if double checked + locking is unsafe. + * tests/thread-test.c: Add tests for g_once(). + 2003-07-02 Matthias Clasen * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4 index 4be1507a..32a940ad 100644 --- a/ChangeLog.pre-2-4 +++ b/ChangeLog.pre-2-4 @@ -1,3 +1,14 @@ +2003-07-09 Matthias Clasen + + Support for one-time initialization functions. (#69668, Sebastian Wilhelmi) + + * configure.in: Check whether double checked locking is safe, define g_once() in + glibconfig.h accordingly. + * glib/gthread.h: Add GOnce, GOnceStatus, G_ONCE_INIT and g_once_impl. + * glib/gthread.c (g_once_impl): Fallback implementation using a mutex if double checked + locking is unsafe. + * tests/thread-test.c: Add tests for g_once(). + 2003-07-02 Matthias Clasen * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 4be1507a..32a940ad 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,14 @@ +2003-07-09 Matthias Clasen + + Support for one-time initialization functions. (#69668, Sebastian Wilhelmi) + + * configure.in: Check whether double checked locking is safe, define g_once() in + glibconfig.h accordingly. + * glib/gthread.h: Add GOnce, GOnceStatus, G_ONCE_INIT and g_once_impl. + * glib/gthread.c (g_once_impl): Fallback implementation using a mutex if double checked + locking is unsafe. + * tests/thread-test.c: Add tests for g_once(). + 2003-07-02 Matthias Clasen * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 4be1507a..32a940ad 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,14 @@ +2003-07-09 Matthias Clasen + + Support for one-time initialization functions. (#69668, Sebastian Wilhelmi) + + * configure.in: Check whether double checked locking is safe, define g_once() in + glibconfig.h accordingly. + * glib/gthread.h: Add GOnce, GOnceStatus, G_ONCE_INIT and g_once_impl. + * glib/gthread.c (g_once_impl): Fallback implementation using a mutex if double checked + locking is unsafe. + * tests/thread-test.c: Add tests for g_once(). + 2003-07-02 Matthias Clasen * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior diff --git a/configure.in b/configure.in index 30d64a7a..7b986f0b 100644 --- a/configure.in +++ b/configure.in @@ -1170,6 +1170,27 @@ esac AC_MSG_RESULT($GIO) AC_SUBST(GIO) +dnl check for cpu to enable double checked locking when possible +dnl ************************************************************ + +if test x"$have_threads" != xno; then + AC_MSG_CHECKING(whether double checked locking is safe) + # According to glibc/linuxthreads the following platforms do + # not have the notion of a read or write memory barrier and + # therefore the double checked locking should be safe. Have a + # look at pthread_once in glibc/linuxthreads/mutex.c to see, + # what this means. + case $host_cpu in + arm|hppa|i386|i686|ia64|m68k|sh|cris|x86_64) + g_use_double_checked_locking=yes + ;; + *) + g_use_double_checked_locking=no + ;; + esac + AC_MSG_RESULT($g_use_double_checked_locking) +fi + dnl **************************************** dnl *** platform dependent source checks *** dnl **************************************** @@ -2134,9 +2155,9 @@ struct _GStaticMutex } static_mutex; }; #define G_STATIC_MUTEX_INIT { NULL, { { $g_mutex_contents} } } -#define g_static_mutex_get_mutex(mutex) \ - (g_thread_use_default_impl ? ((GMutex*) &((mutex)->static_mutex)) : \ - g_static_mutex_get_mutex_impl (&((mutex)->runtime_mutex))) +#define g_static_mutex_get_mutex(mutex) \\ + (g_thread_use_default_impl ? ((GMutex*) &((mutex)->static_mutex)) : \\ + g_static_mutex_get_mutex_impl_shortcut (&((mutex)->runtime_mutex))) _______EOF else cat >>$outfile <<_______EOF @@ -2144,10 +2165,29 @@ $g_enable_threads_def G_THREADS_ENABLED #define G_THREADS_IMPL_$g_threads_impl_def typedef struct _GMutex* GStaticMutex; #define G_STATIC_MUTEX_INIT NULL -#define g_static_mutex_get_mutex(mutex) (g_static_mutex_get_mutex_impl (mutex)) +#define g_static_mutex_get_mutex(mutex) \\ + (g_static_mutex_get_mutex_impl_shortcut (mutex)) _______EOF fi + if test x$g_use_double_checked_locking = xyes; then + cat >>$outfile <<_______EOF +/* double checked locking can be used on this platform */ +#define g_once(once, func, arg) \\ + ((once)->status == G_ONCE_STATUS_READY ? (once)->retval : \\ + g_once_impl (once, func, arg)); +#define g_static_mutex_get_mutex_impl_shortcut(mutex) \\ + (*(mutex) ? *(mutex) : g_static_mutex_get_mutex_impl (mutex)) +_______EOF + else + cat >>$outfile <<_______EOF +/* double checked locking is unsafe to use on this platform, do full locking */ +#define g_once(once, func, arg) (g_once_impl(once, func, arg)) +#define g_static_mutex_get_mutex_impl_shortcut(mutex) \\ + (g_static_mutex_get_mutex_impl (mutex)) +_______EOF +fi + cat >>$outfile <<_______EOF /* This represents a system thread as used by the implementation. An * alien implementaion, as loaded by g_thread_init can only count on @@ -2426,6 +2466,7 @@ xno) g_enable_threads_def="#undef";; esac g_threads_impl_def=$g_threads_impl +g_use_double_checked_locking=$g_use_double_checked_locking g_mutex_has_default="$mutex_has_default" g_mutex_sizeof="$glib_cv_sizeof_gmutex" diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index a100b22b..b50e218f 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -554,6 +554,12 @@ g_static_private_get g_static_private_set g_static_private_free + +GOnce +GOnceStatus +G_ONCE_INIT +g_once + G_THREAD_ECF G_THREAD_CF @@ -569,6 +575,7 @@ g_threads_got_initialized g_thread_functions_for_glib_use g_thread_init_glib g_thread_error_quark +g_once_impl
diff --git a/docs/reference/glib/tmpl/threads.sgml b/docs/reference/glib/tmpl/threads.sgml index b0837a7c..bb5eef73 100644 --- a/docs/reference/glib/tmpl/threads.sgml +++ b/docs/reference/glib/tmpl/threads.sgml @@ -1606,3 +1606,72 @@ you should also free the #GStaticPrivate. @private_key: a #GStaticPrivate to be freed. + + +A GOnce struct controls a one-time initialization function. +Any one-time initialization function must have its own unique GOnce +struct. + + +@Since: 2.4 + + + +The possible stati of a one-time initialization function controlled by a #GOnce struct. + + +@G_ONCE_STATUS_NOTCALLED: the function has not been called yet. +@G_ONCE_STATUS_PROGRESS: the function call is currently in progress. +@G_ONCE_STATUS_READY: the function has been called. + + + +A #GOnce must be initialized with this macro, before it can be used. + + + + +GOnce my_once = G_ONCE_INIT; + + + + + + + + +The first call to this routine by a process with a given #GOnce struct calls @func with the given +argument. Thereafter, subsequent calls to g_once() with the same #GOnce struct do not call @func +again, but return the stored result of the first call. On return from g_once(), the status of @once +will be %G_ONCE_STATUS_READY. + + +For example, a mutex or a thread-specific data key must be created exactly once. In a threaded +environment, calling g_once() ensures that the initialization is serialized across multiple threads. + + +Calling g_once() recursively on the same #GOnce struct in @func will lead to a deadlock. + + + + +gpointer +get_debug_flags () +{ + static GOnce my_once = G_ONCE_INIT; + + g_once (&my_once, parse_debug_flags, NULL); + + return my_once.retval; +} + + + + +@once: a #GOnce structure +@func: the function associated to @once. This function is called only once, regardless of the + number of times it and its associated #GOnce struct are passed to g_once() . +@arg: data to be passed to @func +@Since: 2.4 + + diff --git a/glib/gthread.c b/glib/gthread.c index 20cdf665..138bc32d 100644 --- a/glib/gthread.c +++ b/glib/gthread.c @@ -1,7 +1,7 @@ /* GLIB - Library of useful routines for C programming * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * - * gmutex.c: MT safety related functions + * gthread.c: MT safety related functions * Copyright 1998 Sebastian Wilhelmi; University of Karlsruhe * Owen Taylor * @@ -148,7 +148,8 @@ GThreadFunctions g_thread_functions_for_glib_use = { /* Local data */ -static GMutex *g_mutex_protect_static_mutex_allocation = NULL; +static GMutex *g_once_mutex = NULL; +static GCond *g_once_cond = NULL; static GPrivate *g_thread_specific_private = NULL; static GSList *g_thread_all_threads = NULL; static GSList *g_thread_free_indeces = NULL; @@ -167,7 +168,8 @@ g_thread_init_glib (void) */ GRealThread* main_thread = (GRealThread*) g_thread_self (); - g_mutex_protect_static_mutex_allocation = g_mutex_new (); + g_once_mutex = g_mutex_new (); + g_once_cond = g_cond_new (); _g_convert_thread_init (); _g_rand_thread_init (); @@ -198,6 +200,33 @@ g_thread_init_glib (void) } #endif /* G_THREADS_ENABLED */ +gpointer +g_once_impl (GOnce *once, + GThreadFunc func, + gpointer arg) +{ + g_mutex_lock (g_once_mutex); + + while (once->status == G_ONCE_STATUS_PROGRESS) + g_cond_wait (g_once_cond, g_once_mutex); + + if (once->status != G_ONCE_STATUS_READY) + { + once->status = G_ONCE_STATUS_PROGRESS; + g_mutex_unlock (g_once_mutex); + + once->retval = func (arg); + + g_mutex_lock (g_once_mutex); + once->status = G_ONCE_STATUS_READY; + g_cond_broadcast (g_once_cond); + } + + g_mutex_unlock (g_once_mutex); + + return once->retval; +} + void g_static_mutex_init (GStaticMutex *mutex) { @@ -214,14 +243,23 @@ g_static_mutex_get_mutex_impl (GMutex** mutex) if (!g_thread_supported ()) return NULL; - g_assert (g_mutex_protect_static_mutex_allocation); + g_assert (g_once_mutex); - g_mutex_lock (g_mutex_protect_static_mutex_allocation); + g_mutex_lock (g_once_mutex); if (!(*mutex)) - *mutex = g_mutex_new (); + { + GMutex *new_mutex = g_mutex_new (); + + /* The following is a memory barrier to avoid the write + * to *new_mutex being reordered to after writing *mutex */ + g_mutex_lock (new_mutex); + g_mutex_unlock (new_mutex); + + *mutex = new_mutex; + } - g_mutex_unlock (g_mutex_protect_static_mutex_allocation); + g_mutex_unlock (g_once_mutex); return *mutex; } diff --git a/glib/gthread.h b/glib/gthread.h index 31522033..884d91a9 100644 --- a/glib/gthread.h +++ b/glib/gthread.h @@ -284,6 +284,24 @@ gboolean g_static_rw_lock_writer_trylock (GStaticRWLock* lock); void g_static_rw_lock_writer_unlock (GStaticRWLock* lock); void g_static_rw_lock_free (GStaticRWLock* lock); +typedef enum +{ + G_ONCE_STATUS_NOTCALLED, + G_ONCE_STATUS_PROGRESS, + G_ONCE_STATUS_READY +} GOnceStatus; + +typedef struct _GOnce GOnce; +struct _GOnce +{ + volatile GOnceStatus status; + volatile gpointer retval; +}; + +#define G_ONCE_INIT { G_ONCE_STATUS_NOTCALLED, NULL } + +gpointer g_once_impl (GOnce *once, GThreadFunc func, gpointer arg); + /* these are some convenience macros that expand to nothing if GLib * was configured with --disable-threads. for using StaticMutexes, * you define them with G_LOCK_DEFINE_STATIC (name) or G_LOCK_DEFINE (name) diff --git a/tests/thread-test.c b/tests/thread-test.c index 9a2e7b3b..8bed23bd 100644 --- a/tests/thread-test.c +++ b/tests/thread-test.c @@ -297,14 +297,92 @@ test_g_static_rw_lock () g_assert (test_g_static_rw_lock_state == 0); } +#define G_ONCE_SIZE 100 +#define G_ONCE_THREADS 10 + +G_LOCK_DEFINE (test_g_once); +static guint test_g_once_guint_array[G_ONCE_SIZE]; +static GOnce test_g_once_array[G_ONCE_SIZE]; + +static gpointer +test_g_once_init_func(gpointer arg) +{ + guint *count = arg; + g_usleep (g_random_int_range (20,1000)); + (*count)++; + g_usleep (g_random_int_range (20,1000)); + return arg; +} + +static gpointer +test_g_once_thread (gpointer ignore) +{ + guint i; + G_LOCK (test_g_once); + /* Don't start before all threads are created */ + G_UNLOCK (test_g_once); + for (i = 0; i < 1000; i++) + { + guint pos = g_random_int_range (0, G_ONCE_SIZE); + gpointer ret = g_once (test_g_once_array + pos, test_g_once_init_func, + test_g_once_guint_array + pos); + g_assert (ret == test_g_once_guint_array + pos); + } + + /* Make sure, that all counters are touched at least once */ + for (i = 0; i < G_ONCE_SIZE; i++) + { + gpointer ret = g_once (test_g_once_array + i, test_g_once_init_func, + test_g_once_guint_array + i); + g_assert (ret == test_g_once_guint_array + i); + } + + return NULL; +} + +static void +test_g_thread_once (void) +{ + static GOnce once_init = G_ONCE_INIT; + GThread *threads[G_ONCE_THREADS]; + guint i; + for (i = 0; i < G_ONCE_SIZE; i++) + { + test_g_once_array[i] = once_init; + test_g_once_guint_array[i] = i; + } + G_LOCK (test_g_once); + for (i = 0; i < G_ONCE_THREADS; i++) + { + threads[i] = g_thread_create (test_g_once_thread, (gpointer)(i%2), + TRUE, NULL); + } + G_UNLOCK (test_g_once); + for (i = 0; i < G_ONCE_THREADS; i++) + { + g_thread_join (threads[i]); + } + + for (i = 0; i < G_ONCE_SIZE; i++) + { + g_assert (test_g_once_guint_array[i] == i + 1); + } +} + /* run all the tests */ void run_all_tests() { test_g_mutex (); + g_print ("."); test_g_static_rec_mutex (); + g_print ("."); test_g_static_private (); + g_print ("."); test_g_static_rw_lock (); + g_print ("."); + test_g_thread_once (); + g_print ("."); } int @@ -323,6 +401,7 @@ main (int argc, g_thread_use_default_impl = FALSE; run_all_tests (); + g_print ("\n"); #endif return 0;