From e66277492a559f0df2903e86b5dd909529e96dad Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Thu, 18 Dec 2014 15:37:26 -0500 Subject: [PATCH] GThread: add "critical section" support --- glib/gthread.c | 182 ++++++++++++++++++++++++++++++++++++++++++ glib/gthread.h | 9 ++- glib/gthreadprivate.h | 5 ++ 3 files changed, 195 insertions(+), 1 deletion(-) diff --git a/glib/gthread.c b/glib/gthread.c index ea8e5f965..c193da2d1 100644 --- a/glib/gthread.c +++ b/glib/gthread.c @@ -497,6 +497,7 @@ static void g_thread_cleanup (gpointer data); static GPrivate g_thread_specific_private = G_PRIVATE_INIT (g_thread_cleanup); G_LOCK_DEFINE_STATIC (g_thread_new); +G_LOCK_DEFINE_STATIC (g_thread_critical_section); /* GOnce {{{1 ------------------------------------------------------------- */ @@ -972,6 +973,187 @@ g_thread_self (void) return (GThread*) thread; } +/** + * g_thread_wakeup: + * @thread: a #GThread + * + * Wakes @thread if it is in a critical section. + * + * If @thread is not in a critical section, does nothing. + * + * See g_thread_enter_critical_section_using_handle() for an example of + * how you might use this. + * + * Since: 2.44 + */ +void +g_thread_wakeup (GThread *thread) +{ + GRealThread *real = (GRealThread *) thread; + + G_LOCK (g_thread_critical_section); + + if (real->in_critical && !real->wakeup_flagged) + { + g_wakeup_signal (real->wakeup); + real->wakeup_flagged = TRUE; + } + + G_UNLOCK (g_thread_critical_section); +} + +/** + * g_thread_enter_critical_section_using_handle: + * @thread: the current #GThread + * + * Enters a critical region for the current thread. + * + * @thread absolutely must be equal to the current thread as returned by + * g_thread_self(). The behaviour is completely undefined otherwise. + * + * This function returns a handle that, at some point during the time + * that this function was running, was not ready. + * + * Any call to g_thread_wakeup() that occurs after that point will + * result in the returned handle polling as ready. + * + * For this reason, if a call to g_thread_wakeup() and + * g_thread_enter_critical_section_using_handle() are made + * concurrently, then the returned handled may either by ready or not + * ready. + * + * In order for this to work reliably you will need an extra variable. + * You will need to use either locks or atomic operations to protect + * access to that variable. + * + * You must call g_thread_leave_critical_section() immediately after you + * are done waiting and you must not call any other non-system functions + * in between. The pairing of this call with + * g_thread_leave_critical_section() is not recursive. + * + * Consider the following example: + * + * |[ + * gboolean continue_work; + * GThread *worker; + * + * static gpointer worker (gpointer user_data) { + * GThread *self = g_thread_self (); + * gboolean should_exit = FALSE; + * + * do + * { + * ghandle handle; + * + * handle = g_thread_enter_critical_section_using_handle (self); + * + * if (g_atomic_int_get (&continue_work)) + * do_blocking_work (handle); + * else + * should_exit = TRUE; + * + * g_thread_leave_critical_section (self); + * } + * while (!should_exit); + * + * return NULL; + * } + * + * void start_worker (void) { + * g_atomic_set (&continue_work, 1); + * worker = g_thread_new ("worker", worker, NULL); + * } + * + * void exit_worker (void) { + * g_atomic_int_set (&continue_work, 0); + * g_thread_wakeup (worker); + * g_thread_join (worker); + * } + * ]| + * + * In the example `do_blocking_work()` is a low-level function that will + * always return immediately when `handle` becomes ready. This may be + * based on `poll()`, for example. Once the worker is started, it will + * enter a loop. Each iteration starts the critical section and then + * atomically checks the `continue_worker` flag. If the flag is not + * set, the blocking begins. + * + * When `exit_worker()` is called, the worker may either be inside of + * or outside of the critical section. If it is inside of the critical + * section then g_thread_wakeup() will ensure that the handle becomes + * ready and `do_blocking_work()` will return shortly. In all cases, + * `continue_worker` will be found to be %FALSE during the next loop + * iteration, and it will exit. If the worker was not in the critical + * section then g_thread_wakeup() will do nothing at all. This is why + * the `continue_worker` variable must be checked after entering the + * critical section. + * + * The return value for this function may or may not be the same each + * time you call it. The handle may or may not have been closed and + * reopened. The only guarantee is that the handle was not ready at + * some time during the call. + * + * Returns: a valid #ghandle + * + * Since: 2.44 + */ +ghandle +g_thread_enter_critical_section_using_handle (GThread *thread) +{ + GRealThread *real = (GRealThread *) thread; + + /* NOTE: although the docs disclaim that the same handle is returned + * each time, gmain.c depends on this behaviour, so don't change + * anything here unless you also fix it there. + */ + + /* We could probably do this with atomics fairly easily, but the + * mutex approach is a lot easier to read and will work for now. + */ + G_LOCK (g_thread_critical_section); + + if (!real->wakeup) + real->wakeup = g_wakeup_new (); + + g_assert (!real->in_critical || !real->wakeup_flagged); + real->in_critical = TRUE; + + G_UNLOCK (g_thread_critical_section); + + return g_wakeup_get_handle (real->wakeup); +} + +/** + * g_thread_leave_critical_section: + * @thread: the current #GThread + * + * Leaves the critical section entered by + * g_thread_enter_critical_section_using_handle(). + * + * @thread absolutely must be equal to the current thread as returned by + * g_thread_self(). The behaviour is completely undefined otherwise. + * + * Since: 2.44 + */ +void +g_thread_leave_critical_section (GThread *thread) +{ + GRealThread *real = (GRealThread *) thread; + + G_LOCK (g_thread_critical_section); + + g_assert (real->in_critical); + real->in_critical = FALSE; + + if (real->wakeup_flagged) + { + real->wakeup_flagged = FALSE; + g_wakeup_acknowledge (real->wakeup); + } + + G_UNLOCK (g_thread_critical_section); +} + /** * g_get_num_processors: * diff --git a/glib/gthread.h b/glib/gthread.h index 3f026f631..16786e17d 100644 --- a/glib/gthread.h +++ b/glib/gthread.h @@ -31,6 +31,7 @@ #include #include +#include G_BEGIN_DECLS @@ -157,7 +158,8 @@ GLIB_AVAILABLE_IN_ALL gpointer g_thread_join (GThread *thread); GLIB_AVAILABLE_IN_ALL void g_thread_yield (void); - +GLIB_AVAILABLE_IN_2_44 +void g_thread_wakeup (GThread *thread); GLIB_AVAILABLE_IN_2_32 void g_mutex_init (GMutex *mutex); @@ -263,6 +265,11 @@ void g_once_init_leave (volatile void *location, (g_once_init_leave((location), (gsize) (result))) #endif +GLIB_AVAILABLE_IN_2_44 +ghandle g_thread_enter_critical_section_using_handle (GThread *thread); +GLIB_AVAILABLE_IN_2_44 +void g_thread_leave_critical_section (GThread *thread); + GLIB_AVAILABLE_IN_2_36 guint g_get_num_processors (void); diff --git a/glib/gthreadprivate.h b/glib/gthreadprivate.h index 198b29a6e..643a41d9c 100644 --- a/glib/gthreadprivate.h +++ b/glib/gthreadprivate.h @@ -23,12 +23,17 @@ #define __G_THREADPRIVATE_H__ #include "deprecated/gthread.h" +#include "gwakeup.h" typedef struct _GRealThread GRealThread; struct _GRealThread { GThread thread; + GWakeup *wakeup; + guint in_critical : 1; + guint wakeup_flagged : 1; + gint ref_count; gboolean ours; gchar *name;