Bug 548967 - 1 bit mutex lock

Add support for a mutex lock that consumes only one bit of storage
inside of an integer on systems that support futexes.  Futex is emulated
(at a higher cost) on systems that don't have it -- but only in the
contended case.
This commit is contained in:
Ryan Lortie 2010-01-28 11:32:07 -05:00
parent 595cad2531
commit 669b0f2d6b
9 changed files with 379 additions and 0 deletions

View File

@ -2544,6 +2544,32 @@ fi
AM_CONDITIONAL(HAVE_GCC_BUILTINS_FOR_ATOMIC_OPERATIONS,
[test $glib_cv_gcc_has_builtin_atomic_operations = yes])
dnl ************************
dnl ** Check for futex(2) **
dnl ************************
AC_MSG_CHECKING([for futex(2) system call])
AC_COMPILE_IFELSE([
#include <linux/futex.h>
#include <syscall.h>
#include <unistd.h>
int
main (void)
{
/* it's not like this actually runs or anything... */
syscall (SYS_futex, NULL, FUTEX_WAKE, FUTEX_WAIT);
return 0;
}
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_FUTEX, [test "$have_futex" = "yes"],
[we have the futex(2) system call])
],
[
AC_MSG_RESULT(no)
])
dnl ****************************************
dnl *** GLib POLL* compatibility defines ***
dnl ****************************************

View File

@ -660,6 +660,11 @@ g_once
g_once_init_enter
g_once_init_leave
<SUBSECTION>
g_bit_lock
g_bit_trylock
g_bit_unlock
<SUBSECTION Private>
G_THREAD_ECF
G_THREAD_CF

View File

@ -109,6 +109,7 @@ libglib_2_0_la_SOURCES = \
$(gatomic_c) \
gbacktrace.c \
gbase64.c \
gbitlock.c \
gbookmarkfile.c \
gbsearcharray.h \
gcache.c \
@ -196,6 +197,7 @@ glibsubinclude_HEADERS = \
gatomic.h \
gbacktrace.h \
gbase64.h \
gbitlock.h \
gbookmarkfile.h \
gcache.h \
gchecksum.h \

292
glib/gbitlock.c Normal file
View File

@ -0,0 +1,292 @@
/*
* Copyright © 2008 Ryan Lortie
* Copyright © 2010 Codethink Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the licence, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
#include "gbitlock.h"
#include <glib/gatomic.h>
#include <glib/gslist.h>
#include <glib/gthread.h>
#include "gthreadprivate.h"
#include "config.h"
#include "galias.h"
#ifndef HAVE_FUTEX
static GSList *g_futex_address_list = NULL;
static GMutex *g_futex_mutex = NULL;
#endif
void
_g_futex_thread_init (void) {
#ifndef HAVE_FUTEX
g_futex_mutex = g_mutex_new ();
#endif
}
#ifdef HAVE_FUTEX
/*
* We have headers for futex(2) on the build machine. This does not
* imply that every system that ever runs the resulting glib will have
* kernel support for futex, but you'd have to have a pretty old
* kernel in order for that not to be the case.
*
* If anyone actually gets bit by this, please file a bug. :)
*/
#include <linux/futex.h>
#include <syscall.h>
#include <unistd.h>
/* < private >
* g_futex_wait:
* @address: a pointer to an integer
* @value: the value that should be at @address
*
* Atomically checks that the value stored at @address is equal to
* @value and then blocks. If the value stored at @address is not
* equal to @value then this function returns immediately.
*
* To unblock, call g_futex_wake() on @address.
*
* This call may spuriously unblock (for example, in response to the
* process receiving a signal) but this is not guaranteed. Unlike the
* Linux system call of a similar name, there is no guarantee that a
* waiting process will unblock due to a g_futex_wake() call in a
* separate process.
*/
static void
g_futex_wait (const volatile gint *address,
gint value)
{
syscall (SYS_futex, address, (gsize) FUTEX_WAIT, (gsize) value, NULL);
}
/* < private >
* g_futex_wake:
* @address: a pointer to an integer
*
* Nominally, wakes one thread that is blocked in g_futex_wait() on
* @address (if any thread is currently waiting).
*
* As mentioned in the documention for g_futex_wait(), spurious
* wakeups may occur. As such, this call may result in more than one
* thread being woken up.
*/
static void
g_futex_wake (const volatile gint *address)
{
syscall (SYS_futex, address, (gsize) FUTEX_WAKE, (gsize) 1, NULL);
}
#else
/* emulate futex(2) */
typedef struct
{
const volatile gint *address;
gint ref_count;
GCond *wait_queue;
} WaitAddress;
static GSList *g_futex_address_list;
static GMutex *g_futex_mutex;
static WaitAddress *
g_futex_find_address (const volatile gint *address)
{
GSList *node;
for (node = g_futex_address_list; node; node = node->next)
{
WaitAddress *waiter = node->data;
if (waiter->address == address)
return waiter;
}
return NULL;
}
static void
g_futex_wait (const volatile gint *address,
gint value)
{
g_mutex_lock (g_futex_mutex);
if G_LIKELY (g_atomic_int_get (address) == value)
{
WaitAddress *waiter;
if ((waiter = g_futex_find_address (address)) == NULL)
{
waiter = g_slice_new (WaitAddress);
waiter->address = address;
waiter->wait_queue = g_cond_new ();
waiter->ref_count = 0;
g_futex_address_list =
g_slist_prepend (g_futex_address_list, waiter);
}
waiter->ref_count++;
g_cond_wait (waiter->wait_queue, g_futex_mutex);
if (!--waiter->ref_count)
{
g_futex_address_list =
g_slist_remove (g_futex_address_list, waiter);
g_cond_free (waiter->wait_queue);
g_slice_free (WaitAddress, waiter);
}
}
g_mutex_unlock (g_futex_mutex);
}
static void
g_futex_wake (const volatile gint *address)
{
WaitAddress *waiter;
/* need to lock here for two reasons:
* 1) need to acquire/release lock to ensure waiter is not in
* the process of registering a wait
* 2) need to -stay- locked until the end to ensure a wake()
* in another thread doesn't cause 'waiter' to stop existing
*/
g_mutex_lock (g_futex_mutex);
if ((waiter = g_futex_find_address (address)))
g_cond_signal (waiter->wait_queue);
g_mutex_unlock (g_futex_mutex);
}
#endif
#define CONTENTION_CLASSES 11
static volatile gint g_bit_lock_contended[CONTENTION_CLASSES];
/**
* g_bit_lock:
* @address: a pointer to an integer
* @lock_bit: a bit value between 0 and 31
*
* Sets the indicated @lock_bit in @address. If the bit is already
* set, this call will block until g_bit_unlock() unsets the
* corresponding bit.
*
* Attempting to lock on two different bits within the same integer is
* not supported and will very probably cause deadlocks.
*
* The value of the bit that is set is (1u << @bit). If @bit is not
* between 0 and 31 then the result is undefined.
*
* This function accesses @address atomically. All other accesses to
* @address must be atomic in order for this function to work
* reliably.
**/
void
g_bit_lock (volatile gint *address,
gint lock_bit)
{
guint v;
retry:
v = g_atomic_int_get (address);
if (v & (1u << lock_bit))
/* already locked */
{
guint class = ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
g_atomic_int_add (&g_bit_lock_contended[class], +1);
g_futex_wait (address, v);
g_atomic_int_add (&g_bit_lock_contended[class], -1);
goto retry;
}
if (!g_atomic_int_compare_and_exchange (address, v, v | (1u << lock_bit)))
goto retry;
}
/**
* g_bit_trylock:
* @address: a pointer to an integer
* @lock_bit: a bit value between 0 and 31
*
* Sets the indicated @lock_bit in @address, returning %TRUE if
* successful. If the bit is already set, returns %FALSE immediately.
*
* Attempting to lock on two different bits within the same integer is
* not supported.
*
* The value of the bit that is set is (1u << @bit). If @bit is not
* between 0 and 31 then the result is undefined.
*
* This function accesses @address atomically. All other accesses to
* @address must be atomic in order for this function to work
* reliably.
**/
gboolean
g_bit_trylock (volatile gint *address,
gint lock_bit)
{
guint v;
retry:
v = g_atomic_int_get (address);
if (v & (1u << lock_bit))
/* already locked */
return FALSE;
if (!g_atomic_int_compare_and_exchange (address, v, v | (1u << lock_bit)))
goto retry;
return TRUE;
}
/**
* g_bit_unlock:
* @address: a pointer to an integer
* @lock_bit: a bit value between 0 and 31
*
* Clears the indicated @lock_bit in @address. If another thread is
* currently blocked in g_bit_lock() on this same bit then it will be
* woken up.
*
* This function accesses @address atomically. All other accesses to
* @address must be atomic in order for this function to work
* reliably.
**/
void
g_bit_unlock (volatile gint *address,
gint lock_bit)
{
guint class = ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
guint v;
retry:
v = g_atomic_int_get (address);
if (!g_atomic_int_compare_and_exchange (address, v, v & ~(1u << lock_bit)))
goto retry;
if (g_atomic_int_get (&g_bit_lock_contended[class]))
g_futex_wake (address);
}
#define __G_BITLOCK_C__
#include "galiasdef.c"

43
glib/gbitlock.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Copyright © 2008 Ryan Lortie
* Copyright © 2010 Codethink Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the licence, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
#ifndef __G_BITLOCK_H__
#define __G_BITLOCK_H__
#include <glib/gtypes.h>
#if defined(G_DISABLE_SINGLE_INCLUDES) && !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
#error "Only <glib.h> can be included directly."
#endif
G_BEGIN_DECLS
void g_bit_lock (volatile gint *lock,
gint lock_bit);
gboolean g_bit_trylock (volatile gint *lock,
gint lock_bit);
void g_bit_unlock (volatile gint *lock,
gint lock_bit);
G_END_DECLS
#endif /* __G_BITLOCK_H_ */

View File

@ -35,6 +35,7 @@
#include <glib/gatomic.h>
#include <glib/gbacktrace.h>
#include <glib/gbase64.h>
#include <glib/gbitlock.h>
#include <glib/gbookmarkfile.h>
#include <glib/gcache.h>
#include <glib/gchecksum.h>

View File

@ -1264,6 +1264,14 @@ g_str_hash
#endif
#endif
#if IN_HEADER(__G_BITLOCK_H__)
#if IN_FILE(__G_BITLOCK_C__)
g_bit_lock
g_bit_trylock
g_bit_unlock
#endif
#endif
#if IN_HEADER(__G_THREAD_H__)
#if IN_FILE(__G_THREAD_C__)
g_once_impl

View File

@ -166,6 +166,7 @@ g_thread_init_glib (void)
_g_rand_thread_init ();
_g_main_thread_init ();
_g_utils_thread_init ();
_g_futex_thread_init ();
#ifdef G_OS_WIN32
_g_win32_thread_init ();
#endif

View File

@ -58,6 +58,7 @@ G_GNUC_INTERNAL void _g_rand_thread_init (void);
G_GNUC_INTERNAL void _g_main_thread_init (void);
G_GNUC_INTERNAL void _g_atomic_thread_init (void);
G_GNUC_INTERNAL void _g_utils_thread_init (void);
G_GNUC_INTERNAL void _g_futex_thread_init (void);
#ifdef G_OS_WIN32
G_GNUC_INTERNAL void _g_win32_thread_init (void);