next round: glib thread safety proposal



Hi,

after some discussion with Owen, I've now changed my mind for the n'th
time (n >> 1) and will make yet another proposal:

The aims:

- make glib reentrant (dont build two versions, one reentrant, the other 
  nonereentrant)

- don't make glib require any additional linking, because 
  additional thread libs might contain unwanted startup code etc.

- don't overuse special features of the target os, compiler and assembler. 
  (we will however not be able to do without them for convenience reasons,
   say weak linkage)

- allow programs using glib to change the used mutex/condition 
  implementation. This is desirable beacuse e.g. netscape comes with its
own
  thread lib.

The proposed solution:

I define the following struct in glib.h

typedef struct _GMutexFunctions GMutexFunctions;
struct _GMutexFunctions
{
  gpointer (*mutex_new)       ();
  void     (*mutex_lock)      (gpointer mutex);
  gboolean (*mutex_trylock)   (gpointer mutex);
  void     (*mutex_unlock)    (gpointer mutex);
  void     (*mutex_free)      (gpointer mutex);
  gpointer (*cond_new)        ();
  void     (*cond_signal)     (gpointer cond);
  void     (*cond_broadcast)  (gpointer cond);
  void     (*cond_wait)       (gpointer cond, gpointer mutex);
  gboolean (*cond_timed_wait) (gpointer cond, gpointer mutex, gulong
duration);
  void     (*cond_free)       (gpointer cond);
};

That struct determines, what mutex/condition implementation will be used.
This goes along the lines of
ftp://ftp.gtk.org/pub/gtk/patches/gtk-rao-980914-0.patch.* 

I only extended the interface, because, if gtk get a finer grained mutex
use, there might arise the need for conditions, so I put them here right
away. There is however no thread creation function etc. This should be
done in a dedicated library or be added here later. We'll see.

Then we also have the following declarations and defines in glib.h:

extern GMutexFunctions g_mutex_functions_for_glib_use;

/* This function is to set the mutex functions, MUST BE CALLED AS THE
FIRST  
   GLIB FUNCTION, AT MAXIMUM ONCE */
void g_mutex_functions_set(GMutexFunctions* init);

#define G_USE_WEAK_EXTERN(name,fail,arg) \
  ( ( name == NULL ) ? (fail): (*name)arg )
#define G_USE_MUTEX_FUNC(name,fail,arg) \
  G_USE_WEAK_EXTERN(g_mutex_functions_for_glib_use.name,fail,arg)

#define g_mutex_new() G_USE_MUTEX_FUNC(mutex_new,NULL,())
#define g_mutex_lock(mutex) G_USE_MUTEX_FUNC(mutex_lock,0,(mutex))
#define g_mutex_trylock(mutex)
G_USE_MUTEX_FUNC(mutex_trylock,TRUE,(mutex))
#define g_mutex_unlock(mutex) G_USE_MUTEX_FUNC(mutex_unlock,0,(mutex))
#define g_mutex_free(mutex) G_USE_MUTEX_FUNC(mutex_free,0,(mutex))
#define g_cond_new() G_USE_MUTEX_FUNC(cond_new,NULL,())
#define g_cond_signal(cond) G_USE_MUTEX_FUNC(cond_signal,0,(cond))
#define g_cond_broadcast(cond) G_USE_MUTEX_FUNC(cond_broadcast,0,(cond))
#define g_cond_wait(cond,mutex) G_USE_MUTEX_FUNC(cond_wait,0,(cond,mutex))
#define g_cond_timed_wait(cond,mutex,duration) \
  G_USE_MUTEX_FUNC(cond_timed_wait,TRUE,(cond,mutex,duration))
#define g_cond_free(cond) G_USE_MUTEX_FUNC(cond_free,0,(cond))

Then of course there is an extra file gmutex.c, that implements the whole
thing. There is a constructor function "__attribute ((constructor))", that
tries to find out, if the symbols for the pthread implementations are
defined and enters them into the g_mutex_functions_for_glib_use function
table to provide some sane default. This however requires weak symbols. I
have cut'n'pasted something from the glibc to achieve this and it is
working here on solaris. we would have to add some code to configure.in
though. The init functions could of course be extendened to automatically
look for other implementations too. attached is the file gmutex.c and the
patch to glib.h and Makefile.am. On platforms without weak symbols we
could do the following: provide a macro: G_MUTEX_STANDARD_IMPLEMENTATION
that provides a complete implementation of all functions (so we have a
deferred binding of the offending symbols, only in application code, not
in library code) and sets the function table accordingly. The whole thing
happens only if weak symbols are not possible on this platforms, otherwise
the macro is just empty. Thus If you include this macro somewehre at the
toplevel in you program (but only in one module) in a multithreaded
program using glib, you get all you want.

So do something like:

G_MUTEX_STANDARD_IMPLEMENTATION;

int
main()
{
  /* working thread safe on platforms with and without weak symbols */
}

Such a prog of course has to be linked with the apropriate thread library,
whereas other programs using glib needn't. I have not written that down so
far, but if there will be some consensus on the proposed thechnique, I'll
do a nice framework. (including configure.in stuff). We might also define
some opaque types GMutex, GCondition and use GMutex* and GCondition* resp.
instead of gpointer to make the whole thing look more like glib. Also
inline code on compilers that does support them will be better than
defines, it generally makes error messages much simpler to parse for
humans.

NB: Of course no work on actual protecting the static vars etc. in glib is
done here, but wasn't somebody (Jeff Garzik, can't remeber right now)
already doing it. This is only to lay sound groundings for the whole
thing. 

BTW.: This technique also allowes to write 'out of the box' MT-safe
libraries using glib.

Bye,
Sebastian

-- 
+--------------============####################============--------------+
| Sebastian Wilhelmi, Institute for Computer Design and Fault Tolerance, |
| University of Karlsruhe;  Building 20.20, Room 263,  D-76128 Karlsruhe |
| mail: wilhelmi@ira.uka.de;  fax: +49 721 370455;  fon: +49 721 6084353 |
+-----------------> http://goethe.ira.uka.de/~wilhelmi <-----------------+
Index: Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/Makefile.am,v
retrieving revision 1.14
diff -u -r1.14 Makefile.am
--- Makefile.am	1998/11/12 04:28:49	1.14
+++ Makefile.am	1998/11/19 15:41:30
@@ -37,6 +37,7 @@
 		glist.c		\
 		gmem.c		\
 		gmessages.c	\
+		gmutex.c	\
 		gnode.c		\
 		gprimes.c	\
 		gslist.c	\
Index: glib.h
===================================================================
RCS file: /cvs/gnome/glib/glib.h,v
retrieving revision 1.73
diff -u -r1.73 glib.h
--- glib.h	1998/11/16 07:45:10	1.73
+++ glib.h	1998/11/19 15:41:32
@@ -2194,6 +2194,43 @@
 
 #endif /* NATIVE_WIN32 */
 
+typedef struct _GMutexFunctions GMutexFunctions;
+struct _GMutexFunctions
+{
+  gpointer (*mutex_new)       ();
+  void     (*mutex_lock)      (gpointer mutex);
+  gboolean (*mutex_trylock)   (gpointer mutex);
+  void     (*mutex_unlock)    (gpointer mutex);
+  void     (*mutex_free)      (gpointer mutex);
+  gpointer (*cond_new)        ();
+  void     (*cond_signal)     (gpointer cond);
+  void     (*cond_broadcast)  (gpointer cond);
+  void     (*cond_wait)       (gpointer cond, gpointer mutex);
+  gboolean (*cond_timed_wait) (gpointer cond, gpointer mutex, gulong duration);
+  void     (*cond_free)       (gpointer cond);
+};
+
+extern GMutexFunctions g_mutex_functions_for_glib_use;
+
+void g_mutex_functions_set(GMutexFunctions* init);
+
+#define G_USE_WEAK_EXTERN(name,fail,arg) \
+  ( ( name == NULL ) ? (fail): (*name)arg )
+#define G_USE_MUTEX_FUNC(name,fail,arg) \
+  G_USE_WEAK_EXTERN(g_mutex_functions_for_glib_use.name,fail,arg)
+
+#define g_mutex_new() G_USE_MUTEX_FUNC(mutex_new,NULL,())
+#define g_mutex_lock(mutex) G_USE_MUTEX_FUNC(mutex_lock,0,(mutex))
+#define g_mutex_trylock(mutex) G_USE_MUTEX_FUNC(mutex_trylock,TRUE,(mutex))
+#define g_mutex_unlock(mutex) G_USE_MUTEX_FUNC(mutex_unlock,0,(mutex))
+#define g_mutex_free(mutex) G_USE_MUTEX_FUNC(mutex_free,0,(mutex))
+#define g_cond_new() G_USE_MUTEX_FUNC(cond_new,NULL,())
+#define g_cond_signal(cond) G_USE_MUTEX_FUNC(cond_signal,0,(cond))
+#define g_cond_broadcast(cond) G_USE_MUTEX_FUNC(cond_broadcast,0,(cond))
+#define g_cond_wait(cond,mutex) G_USE_MUTEX_FUNC(cond_wait,0,(cond,mutex))
+#define g_cond_timed_wait(cond,mutex,duration) \
+  G_USE_MUTEX_FUNC(cond_timed_wait,TRUE,(cond,mutex,duration))
+#define g_cond_free(cond) G_USE_MUTEX_FUNC(cond_free,0,(cond))
 
 #ifdef __cplusplus
 }
/* from config.h we need G_HAVE_ASM_WEAK_DIRECTIVE,
   G_HAVE_ASM_WEAKEXT_DIRECTIVE, G_SYMBOL_PREFIX, this weak external
   symbol stuff is taken from glic, the configure.in test could be as
   well, I'll now just hardcode the solaris version. This should be
   similiar on linux actually, i.e. ELF + gnu ld */

#include <glib.h>

GMutexFunctions g_mutex_functions_for_glib_use; /* is NULLified as default */

#define G_HAVE_ASM_WEAK_DIRECTIVE 1 
#define G_SYMBOL_PREFIX ""
#define G_HAVE_POSIX_THREADS

#if defined (G_HAVE_ASM_WEAK_DIRECTIVE) 
#  define G_WEAK_EXTERN(symbol) asm (".weak " G_SYMBOL_PREFIX #symbol);
#  define G_HAVE_WEAK_EXTERN 1
#elif defined (G_HAVE_ASM_WEAKEXT_DIRECTIVE)
#  define G_WEAK_EXTERN(symbol) asm (".weakext " G_SYMBOL_PREFIX #symbol);
#  define G_HAVE_WEAK_EXTERN 1
#else
#  define G_WEAK_EXTERN(symbol) /* do nothing, will not be weak though */
#  undef G_HAVE_WEAK_EXTERN
#endif

#ifdef G_HAVE_WEAK_EXTERN

#ifdef G_HAVE_POSIX_THREADS

#include <pthread.h>
#include <errno.h>

/* if its not weak, then we can't do anything here, because we don't
   want the link with glib but without -lpthread to fail because of
   this. Now its up to the user to define the functions, if he wants
   them */

#define posix_print_error( name, num )                          \
  g_error( "file %s: line %d (%s): error %s during %s",         \
           __FILE__, __LINE__, G_GNUC_PRETTY_FUNCTION,          \
           g_strerror((num)), #name )

#define posix_check_for_error( what ) G_STMT_START{             \
  int error = (what);                                           \
  if( error ) { posix_print_error( what, error ); }             \
  }G_STMT_END

static gpointer 
g_mutex_new_posix_impl()
{
  gpointer result = g_new0 (pthread_mutex_t, 1);
  G_WEAK_EXTERN(pthread_mutex_init);
  posix_check_for_error( pthread_mutex_init( result, NULL ) );
  return result;
}

static void
g_mutex_free_posix_impl(gpointer mutex)
{
  G_WEAK_EXTERN(pthread_mutex_destroy);
  posix_check_for_error( pthread_mutex_destroy(mutex) );
  g_free (mutex);  
}

/* pthread_mutex_lock, pthread_mutex_unlock can be taken directly, as
   signature and semantic are right, but without error check then!!!!,
   we might want to change this therfore. */

static gboolean
g_mutex_trylock_posix_impl(gpointer mutex)
{
  int result;
  G_WEAK_EXTERN(pthread_mutex_trylock);
  result = pthread_mutex_trylock(mutex);
  if( result == EBUSY )
    {
      return FALSE;
    }
  posix_check_for_error( result );
  return TRUE;
}

static gpointer
g_cond_new_posix_impl()
{
  gpointer result = g_new0 (pthread_cond_t, 1);
  G_WEAK_EXTERN(pthread_cond_init);
  posix_check_for_error( pthread_cond_init( result, NULL ) );
  return result;
}

/* pthread_cond_signal, pthread_cond_broadcast and pthread_cond_wait
   can be taken directly, as signature and semantic are right, but
   without error check then!!!!, we might want to change this
   therfore. */

#define G_MICROSEC 1000000
#define G_NANOSEC 1000000000

static gboolean 
g_cond_timed_wait_posix_impl(gpointer cond, gpointer entered_mutex, 
			     gulong duration)
{
  int result;
  timestruc_t end_time;
  struct timeval current_time;
  gboolean timed_out;
  G_WEAK_EXTERN(pthread_cond_timedwait);
  g_assert( !gettimeofday( &current_time, NULL) );
  end_time.tv_sec = current_time.tv_sec + duration / G_MICROSEC;
  end_time.tv_nsec = ( current_time.tv_usec + duration % G_MICROSEC ) 
    * ( G_NANOSEC / G_MICROSEC );
  while( end_time.tv_nsec >= G_NANOSEC )
    {
      end_time.tv_sec++;
      end_time.tv_nsec-= G_NANOSEC;
    }
  result = pthread_cond_timedwait( cond, entered_mutex, &end_time );
  timed_out = ( result == ETIMEDOUT );
  if( !timed_out ) posix_check_for_error( result );
  return result;
}

static void 
g_cond_free_posix_impl(gpointer cond)
{
  G_WEAK_EXTERN(pthread_cond_destroy);
  posix_check_for_error( pthread_cond_destroy(cond) );
  g_free (cond);  
}

#endif /* G_HAVE_POSIX_THREADS */

static void g_init_mutexes_internal() __attribute__ ((constructor));

static void g_init_mutexes_internal()
{
#ifdef G_HAVE_POSIX_THREADS
  G_WEAK_EXTERN(pthread_mutex_init);
  G_WEAK_EXTERN(pthread_mutex_lock);
  G_WEAK_EXTERN(pthread_mutex_trylock);
  G_WEAK_EXTERN(pthread_mutex_unlock);
  G_WEAK_EXTERN(pthread_mutex_destroy);
  G_WEAK_EXTERN(pthread_cond_init);
  G_WEAK_EXTERN(pthread_cond_signal);
  G_WEAK_EXTERN(pthread_cond_broadcast);
  G_WEAK_EXTERN(pthread_cond_wait);
  G_WEAK_EXTERN(pthread_cond_timedwait);
  G_WEAK_EXTERN(pthread_cond_destroy);
#endif /* G_HAVE_POSIX_THREADS */

#ifdef G_HAVE_WHATEVER_IMPLEMENTATION
  G_WEAK_EXTERN(what ever functions needs to be weak external)
#endif /* G_HAVE_WHATEVER_IMPLEMENTATION */

#ifdef G_HAVE_POSIX_THREADS
  if( pthread_mutex_init && pthread_mutex_lock && pthread_mutex_trylock && 
      pthread_mutex_unlock && pthread_mutex_destroy && 
      pthread_cond_init && pthread_cond_signal && pthread_cond_broadcast &&
      pthread_cond_wait && pthread_cond_timedwait && pthread_cond_destroy )
    /* test, if all are defined, might be to paranoic, but doesnt
       cost that much, so its worth it */
   {
     g_mutex_functions_for_glib_use.mutex_new = g_mutex_new_posix_impl;
     g_mutex_functions_for_glib_use.mutex_lock = pthread_mutex_lock;
     g_mutex_functions_for_glib_use.mutex_trylock = g_mutex_trylock_posix_impl;
     g_mutex_functions_for_glib_use.mutex_unlock = pthread_mutex_unlock;
     g_mutex_functions_for_glib_use.mutex_free = g_mutex_free_posix_impl;
     g_mutex_functions_for_glib_use.cond_new = g_cond_new_posix_impl;
     g_mutex_functions_for_glib_use.cond_signal = pthread_cond_signal; 
     g_mutex_functions_for_glib_use.cond_broadcast = pthread_cond_broadcast;
     g_mutex_functions_for_glib_use.cond_wait = pthread_cond_wait;
     g_mutex_functions_for_glib_use.cond_timed_wait = 
       g_cond_timed_wait_posix_impl;
     g_mutex_functions_for_glib_use.cond_free = g_cond_free_posix_impl;

   }
#endif /* G_HAVE_POSIX_THREADS */

#ifdef G_HAVE_WHATEVER_IMPLEMENTATION
  if( /* look for other implementations, i.e. solaris, vxworks, dce,
	 win32 ... */ )
    {
      /* set them */
    }
#endif /* G_HAVE_WHATEVER_IMPLEMENTATION */
  /* else not needed, as g_mutex_functions_for_glib_use already is NULLed. */
}

#endif

void 
g_mutex_functions_set(GMutexFunctions* init)
{
  g_mutex_functions_for_glib_use = *init;
}



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