+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
+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
+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
+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
+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
+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
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 ****************************************
} 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
#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
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"
g_static_private_set
g_static_private_free
+<SUBSECTION>
+GOnce
+GOnceStatus
+G_ONCE_INIT
+g_once
+
<SUBSECTION Private>
G_THREAD_ECF
G_THREAD_CF
g_thread_functions_for_glib_use
g_thread_init_glib
g_thread_error_quark
+g_once_impl
</SECTION>
<SECTION>
@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
+
+
/* 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
*
/* 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;
*/
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 ();
}
#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)
{
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;
}
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)
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
g_thread_use_default_impl = FALSE;
run_all_tests ();
+ g_print ("\n");
#endif
return 0;