Support for one-time initialization functions. (#69668, Sebastian

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().
This commit is contained in:
Matthias Clasen 2003-07-08 23:43:48 +00:00 committed by Matthias Clasen
parent 238c7c368b
commit 876f907863
12 changed files with 329 additions and 11 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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"

View 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>

View 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

View 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;
}

View 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)

View 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;