Support for one-time initialization functions. (#69668, Sebastian
authorMatthias Clasen <maclas@gmx.de>
Tue, 8 Jul 2003 23:43:48 +0000 (23:43 +0000)
committerMatthias Clasen <matthiasc@src.gnome.org>
Tue, 8 Jul 2003 23:43:48 +0000 (23:43 +0000)
2003-07-09  Matthias Clasen  <maclas@gmx.de>

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().

12 files changed:
ChangeLog
ChangeLog.pre-2-10
ChangeLog.pre-2-12
ChangeLog.pre-2-4
ChangeLog.pre-2-6
ChangeLog.pre-2-8
configure.in
docs/reference/glib/glib-sections.txt
docs/reference/glib/tmpl/threads.sgml
glib/gthread.c
glib/gthread.h
tests/thread-test.c

index 4be1507..32a940a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2003-07-09  Matthias Clasen  <maclas@gmx.de>
+
+       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  <maclas@gmx.de>
 
        * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior 
index 4be1507..32a940a 100644 (file)
@@ -1,3 +1,14 @@
+2003-07-09  Matthias Clasen  <maclas@gmx.de>
+
+       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  <maclas@gmx.de>
 
        * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior 
index 4be1507..32a940a 100644 (file)
@@ -1,3 +1,14 @@
+2003-07-09  Matthias Clasen  <maclas@gmx.de>
+
+       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  <maclas@gmx.de>
 
        * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior 
index 4be1507..32a940a 100644 (file)
@@ -1,3 +1,14 @@
+2003-07-09  Matthias Clasen  <maclas@gmx.de>
+
+       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  <maclas@gmx.de>
 
        * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior 
index 4be1507..32a940a 100644 (file)
@@ -1,3 +1,14 @@
+2003-07-09  Matthias Clasen  <maclas@gmx.de>
+
+       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  <maclas@gmx.de>
 
        * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior 
index 4be1507..32a940a 100644 (file)
@@ -1,3 +1,14 @@
+2003-07-09  Matthias Clasen  <maclas@gmx.de>
+
+       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  <maclas@gmx.de>
 
        * glib/gstrfuncs.c (g_strfreev): Move docs inline, document behavior 
index 30d64a7..7b986f0 100644 (file)
@@ -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"
index a100b22..b50e218 100644 (file)
@@ -554,6 +554,12 @@ g_static_private_get
 g_static_private_set
 g_static_private_free
 
+<SUBSECTION>
+GOnce
+GOnceStatus
+G_ONCE_INIT
+g_once
+
 <SUBSECTION Private>
 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
 </SECTION>
 
 <SECTION>
index b0837a7..bb5eef7 100644 (file)
@@ -1606,3 +1606,72 @@ you should also free the #GStaticPrivate.
 @private_key: a #GStaticPrivate to be freed.
 
 
+<!-- ##### STRUCT GOnce ##### -->
+<para>
+A <structname>GOnce</structname> struct controls a one-time initialization function. 
+Any one-time initialization function must have its own unique <structname>GOnce</structname> 
+struct.
+</para>
+@Since: 2.4
+
+<!-- ##### ENUM GOnceStatus ##### -->
+<para>
+The possible stati of a one-time initialization function controlled by a #GOnce struct.
+</para>
+
+@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.
+
+<!-- ##### MACRO G_ONCE_INIT ##### -->
+<para>
+A #GOnce must be initialized with this macro, before it can be used. 
+</para>
+<para>
+<informalexample>
+<programlisting>
+GOnce my_once = G_ONCE_INIT;
+</programlisting>
+</informalexample>
+</para>
+
+
+
+<!-- ##### MACRO g_once ##### -->
+<para>
+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.
+</para>
+<para>
+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.
+</para>
+<note><para>
+Calling g_once() recursively on the same #GOnce struct in @func will lead to a deadlock.
+</para></note>
+<para>
+<informalexample>
+<programlisting>
+gpointer 
+get_debug_flags ()
+{
+  static GOnce my_once = G_ONCE_INIT;
+  
+  g_once (&my_once, parse_debug_flags, NULL);
+
+  return my_once.retval;
+}
+</programlisting>
+</informalexample>
+</para>
+
+@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
+
+
index 20cdf66..138bc32 100644 (file)
@@ -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;
 }
index 3152203..884d91a 100644 (file)
@@ -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)
index 9a2e7b3..8bed23b 100644 (file)
@@ -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;