[glib/wip/ghandle: 8/16] GThread: add "critical section" support



commit e66277492a559f0df2903e86b5dd909529e96dad
Author: Ryan Lortie <desrt desrt ca>
Date:   Thu Dec 18 15:37:26 2014 -0500

    GThread: add "critical section" support

 glib/gthread.c        |  182 +++++++++++++++++++++++++++++++++++++++++++++++++
 glib/gthread.h        |    9 ++-
 glib/gthreadprivate.h |    5 ++
 3 files changed, 195 insertions(+), 1 deletions(-)
---
diff --git a/glib/gthread.c b/glib/gthread.c
index ea8e5f9..c193da2 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 ------------------------------------------------------------- */
 
@@ -973,6 +974,187 @@ g_thread_self (void)
 }
 
 /**
+ * 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:
  *
  * Determine the approximate number of threads that the system will
diff --git a/glib/gthread.h b/glib/gthread.h
index 3f026f6..16786e1 100644
--- a/glib/gthread.h
+++ b/glib/gthread.h
@@ -31,6 +31,7 @@
 
 #include <glib/gatomic.h>
 #include <glib/gerror.h>
+#include <glib/gpoll.h>
 
 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 198b29a..643a41d 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;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]