Index: configure.in
===================================================================
RCS file: /cvs/gnome/glib/configure.in,v
retrieving revision 1.270
diff -p -u -b -B -r1.270 configure.in
--- configure.in	2002/02/21 03:54:32	1.270
+++ configure.in	2002/02/21 15:52:22
@@ -1489,6 +1489,27 @@ GLIB_IF_VAR_EQ(mutex_has_default, yes,
 )
 
 
+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 *** GLib POLL* compatibility defines ***
 dnl ****************************************
@@ -1856,9 +1878,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
@@ -1866,10 +1888,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
@@ -2142,6 +2183,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: glib/gthread.c
===================================================================
RCS file: /cvs/gnome/glib/glib/gthread.c,v
retrieving revision 1.29
diff -p -u -b -B -r1.29 gthread.c
--- glib/gthread.c	2002/02/04 13:49:36	1.29
+++ glib/gthread.c	2002/02/21 15:52:22
@@ -146,7 +146,8 @@ GThreadFunctions g_thread_functions_for_
 
 /* 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;
@@ -170,9 +171,34 @@ g_mutex_init (void)
   G_THREAD_UF (private_set, (g_thread_specific_private, main_thread));
   G_THREAD_UF (thread_self, (&main_thread->system_thread));
 
-  g_mutex_protect_static_mutex_allocation = g_mutex_new ();
+  g_once_mutex = g_mutex_new ();
+  g_once_cond = g_cond_new ();
 }
 
+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)
 {
@@ -189,14 +215,23 @@ g_static_mutex_get_mutex_impl (GMutex** 
   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: glib/gthread.h
===================================================================
RCS file: /cvs/gnome/glib/glib/gthread.h,v
retrieving revision 1.18
diff -p -u -b -B -r1.18 gthread.h
--- glib/gthread.h	2002/02/04 13:49:36	1.18
+++ glib/gthread.h	2002/02/21 15:52:22
@@ -280,6 +280,24 @@ gboolean  g_static_rw_lock_writer_tryloc
 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: tests/thread-test.c
===================================================================
RCS file: /cvs/gnome/glib/tests/thread-test.c,v
retrieving revision 1.9
diff -p -u -b -B -r1.9 thread-test.c
--- tests/thread-test.c	2002/02/15 09:58:49	1.9
+++ tests/thread-test.c	2002/02/21 15:52:23
@@ -294,14 +294,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 
@@ -320,6 +398,7 @@ main (int   argc,
 
   g_thread_use_default_impl = FALSE;
   run_all_tests ();
+  g_print ("\n");
   
 #endif
   return 0;
