[glib/wip/ghandle: 12/16] gthread: tighten critical section api
- From: Ryan Lortie <desrt src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/ghandle: 12/16] gthread: tighten critical section api
- Date: Fri, 19 Dec 2014 17:26:19 +0000 (UTC)
commit 86c991bc2149bd5501be5bbef8a0338e30173871
Author: Ryan Lortie <desrt desrt ca>
Date: Thu Dec 18 20:15:47 2014 -0500
gthread: tighten critical section api
glib/gthread.c | 120 ++++++++++++++++++++++++++++---------------------------
1 files changed, 61 insertions(+), 59 deletions(-)
---
diff --git a/glib/gthread.c b/glib/gthread.c
index c193da2..d548abd 100644
--- a/glib/gthread.c
+++ b/glib/gthread.c
@@ -497,7 +497,6 @@ 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 ------------------------------------------------------------- */
@@ -977,12 +976,21 @@ g_thread_self (void)
* g_thread_wakeup:
* @thread: a #GThread
*
- * Wakes @thread if it is in a critical section.
+ * Wakes @thread, which must currently be in a critical section.
*
- * If @thread is not in a critical section, does nothing.
+ * If @thread is not in a critical section, the behaviour is undefined.
+ * Much worse, if @thread is in a critical section other than the one
+ * that you expect that it to be in, very bad things will happen.
*
- * See g_thread_enter_critical_section_using_handle() for an example of
- * how you might use this.
+ * The only way to get this right is by holding a lock when you call
+ * this function. The lock must be the same lock that was held while
+ * entering the critical section in the other thread and which will be
+ * held again while leaving it. You will need to use some kind of
+ * variable shared under that lock in order to reliably determine when
+ * the thread is in your critical section.
+ *
+ * See g_thread_enter_critical_section_using_handle() for a full
+ * example.
*
* Since: 2.44
*/
@@ -991,82 +999,88 @@ g_thread_wakeup (GThread *thread)
{
GRealThread *real = (GRealThread *) thread;
- G_LOCK (g_thread_critical_section);
+ g_assert (real->in_critical);
- if (real->in_critical && !real->wakeup_flagged)
+ if (!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.
+ * Enters a critical section for the current thread.
+ *
+ * You must be holding a lock while you call this function.
*
* @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.
+ * This function returns a handle that will not poll as ready, but may
+ * become ready after the lock held when calling this function is
+ * released if g_thread_wakeup() is called. g_thread_wakeup() must be
+ * called while holding the same lock as was held when this function was
+ * called.
*
* 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.
+ * are done waiting. The pairing of this call with
+ * g_thread_leave_critical_section() is not recursive and it is your
+ * responsibility to ensure you do not make calls to other code which
+ * may try to use this functionality.
*
* Consider the following example:
*
* |[
- * gboolean continue_work;
+ * GMutex lock;
* GThread *worker;
+ * gboolean in_critical;
+ * gboolean request_exit;
*
* static gpointer worker (gpointer user_data) {
* GThread *self = g_thread_self ();
- * gboolean should_exit = FALSE;
*
- * do
+ * g_mutex_lock (&mutex);
+ *
+ * while (!request_exit)
* {
* ghandle handle;
*
* handle = g_thread_enter_critical_section_using_handle (self);
+ * in_critical = TRUE;
+ *
+ * g_mutex_unlock (&mutex);
+ *
+ * do_blocking_work (handle);
*
- * if (g_atomic_int_get (&continue_work))
- * do_blocking_work (handle);
- * else
- * should_exit = TRUE;
+ * g_mutex_lock (&mutex);
*
* g_thread_leave_critical_section (self);
+ * in_critical = FALSE;
* }
- * while (!should_exit);
+ *
+ * g_mutex_unlock (&mutex);
*
* return NULL;
* }
*
* void start_worker (void) {
- * g_atomic_set (&continue_work, 1);
+ * g_mutex_lock (&lock);
+ * request_exit = FALSE;
+ * g_mutex_unlock (&lock);
+ *
* worker = g_thread_new ("worker", worker, NULL);
* }
*
* void exit_worker (void) {
- * g_atomic_int_set (&continue_work, 0);
- * g_thread_wakeup (worker);
+ * g_mutex_lock (&lock);
+ * request_exit = TRUE;
+ * if (in_critical)
+ * g_thread_wakeup (worker);
+ * g_mutex_unlock (&lock);
+ *
* g_thread_join (worker);
* }
* ]|
@@ -1074,24 +1088,20 @@ g_thread_wakeup (GThread *thread)
* 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.
+ * enter a loop. Each iteration atomically checks the `exit_worker`
+ * variable and starts the critical section. Then 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.
+ * `request_exit` will be found to be %TRUE during the next loop and the
+ * worker will exit.
*
* 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.
+ * reopened. The only guarantee is that the handle will not poll as
+ * ready at the time it is returned.
*
* Returns: a valid #ghandle
*
@@ -1107,19 +1117,12 @@ g_thread_enter_critical_section_using_handle (GThread *thread)
* 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);
}
@@ -1133,6 +1136,9 @@ g_thread_enter_critical_section_using_handle (GThread *thread)
* @thread absolutely must be equal to the current thread as returned by
* g_thread_self(). The behaviour is completely undefined otherwise.
*
+ * You must be holding the same lock that you were holding when you
+ * called g_thread_enter_critical_section_using_handle().
+ *
* Since: 2.44
*/
void
@@ -1140,8 +1146,6 @@ 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;
@@ -1150,8 +1154,6 @@ g_thread_leave_critical_section (GThread *thread)
real->wakeup_flagged = FALSE;
g_wakeup_acknowledge (real->wakeup);
}
-
- G_UNLOCK (g_thread_critical_section);
}
/**
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]