mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-09-27 17:52:58 +02:00
GThread: add "critical section" support
This commit is contained in:
182
glib/gthread.c
182
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);
|
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_new);
|
||||||
|
G_LOCK_DEFINE_STATIC (g_thread_critical_section);
|
||||||
|
|
||||||
/* GOnce {{{1 ------------------------------------------------------------- */
|
/* GOnce {{{1 ------------------------------------------------------------- */
|
||||||
|
|
||||||
@@ -972,6 +973,187 @@ g_thread_self (void)
|
|||||||
return (GThread*) thread;
|
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:
|
* g_get_num_processors:
|
||||||
*
|
*
|
||||||
|
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
#include <glib/gatomic.h>
|
#include <glib/gatomic.h>
|
||||||
#include <glib/gerror.h>
|
#include <glib/gerror.h>
|
||||||
|
#include <glib/gpoll.h>
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
@@ -157,7 +158,8 @@ GLIB_AVAILABLE_IN_ALL
|
|||||||
gpointer g_thread_join (GThread *thread);
|
gpointer g_thread_join (GThread *thread);
|
||||||
GLIB_AVAILABLE_IN_ALL
|
GLIB_AVAILABLE_IN_ALL
|
||||||
void g_thread_yield (void);
|
void g_thread_yield (void);
|
||||||
|
GLIB_AVAILABLE_IN_2_44
|
||||||
|
void g_thread_wakeup (GThread *thread);
|
||||||
|
|
||||||
GLIB_AVAILABLE_IN_2_32
|
GLIB_AVAILABLE_IN_2_32
|
||||||
void g_mutex_init (GMutex *mutex);
|
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)))
|
(g_once_init_leave((location), (gsize) (result)))
|
||||||
#endif
|
#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
|
GLIB_AVAILABLE_IN_2_36
|
||||||
guint g_get_num_processors (void);
|
guint g_get_num_processors (void);
|
||||||
|
|
||||||
|
@@ -23,12 +23,17 @@
|
|||||||
#define __G_THREADPRIVATE_H__
|
#define __G_THREADPRIVATE_H__
|
||||||
|
|
||||||
#include "deprecated/gthread.h"
|
#include "deprecated/gthread.h"
|
||||||
|
#include "gwakeup.h"
|
||||||
|
|
||||||
typedef struct _GRealThread GRealThread;
|
typedef struct _GRealThread GRealThread;
|
||||||
struct _GRealThread
|
struct _GRealThread
|
||||||
{
|
{
|
||||||
GThread thread;
|
GThread thread;
|
||||||
|
|
||||||
|
GWakeup *wakeup;
|
||||||
|
guint in_critical : 1;
|
||||||
|
guint wakeup_flagged : 1;
|
||||||
|
|
||||||
gint ref_count;
|
gint ref_count;
|
||||||
gboolean ours;
|
gboolean ours;
|
||||||
gchar *name;
|
gchar *name;
|
||||||
|
Reference in New Issue
Block a user