Re: Thread specific data for GLib




Sebastian Wilhelmi <wilhelmi@ira.uka.de> writes:

> > In general, I'd like to stick to something a lot closer
> > to the metal. All this StaticPrivate stuff may, IMO,
> > is really over-complicated, and may not match the
> > needs of the application.
> 
> It is complicated for two reasons:
> 
> 1) it has an own managment of data, this is to provide the possibility to
>    destroy all data connected to a key. This can't be done on either
> solaris
>    or posix.
> 2) it provides the additional parameter constructor, that is called,
>    whenever the data, that would be returned is NULL. This is
>    extremly practical, because most of the time you'll find yourself doing
>    something like this:
> 
>    if( (data = g_private_get( key ) ) == NULL )
>       g_private_set( key, data = g_new0(blablub,1) ); 
> 
>    This addition doesn't impose much overhead (it's just one comparison
> for
>    every call, compared to the mutex lock it's just nothing).
> 
> > The question, though, is how to provide a convenient
> > interface to getting a unique thread index to use
> > for your a bit of static data, without requiring
> > locking on each fetch of the static data.
> 
> That simply is not possible without magic (like constructors and the
> like). Period. (Regarding pthread_once see below)

Not so fast. If you preallocate a single TSD key in
g_thread_init(), it is possible to have thread-specific
data that:
 
 1) Does not need to lock a mutex for either get or set.
 2) Is completely portable, assuming that there is
    some integral type that is read/written atomically.
 3) Is quite convenient to use.

The interface is:

static guint myindex = 0;

  setspecific_once (&myindex, avalue);
  [...]
  somevalue = getspecific_once (&myindex);

Implementation below. The extension to
setspecific_once (guint *, GDestroyNotify) is 
straightforward; it could be made prettier
with a few typedefs/#define's

[ 
  You could also use the same basic strategy to build
  a non-locking version of your GStaticMutex, but
  I'm not convinced yet that the constructor 
  functionality is worth the complexity 
]
  
> > Netscape generally uses constructors to set up the
> > the TSD indices; using pthreads you can use
> > pthread_once(). 
> 
> here is a small test, I've done:
> pthread_mutex need 1031 nanoseconds for enter/exit pair.
> pthread_once needs 1327 nanoseconds for call.
> pthread_getspecific needs 3253 nanoseconds for call.
> pthread_setspecific needs 467 nanoseconds for call.
> 
> The program is attached for anyone to try it on his/her platform. I really
> think, its rediculous, that pthread_setspecific is more than 5 times
> faster than pthread_getspecific, and still more than twice as fast as a
> pthread_mutex enter/exit pair. as you see pthread_once is slower than a
> pthread_mutex enter/exit pair. The last should actually also hold true on
> linux, I suppose.

I think it is ridiculous that Sun can't write a decent thread
library ;-) On LinuxThreads, the numbers are:

pthread_mutex need 382 nanoseconds for enter/exit pair.
pthread_once needs 96 nanoseconds for call.
pthread_getspecific needs 120 nanoseconds for call.
pthread_setspecific needs 152 nanoseconds for call.

[ glibc-2.0.7, PII/300, but the relative timings hold on 
  slower machines ]

pthread_once uses the double-checking idiom that
we rejected as being not necessarily portable -
so it's possible that Solaris is always locking
a mutex in its implementation of pthread_once().

But the LinuxThreads numbers for get/setspecific
I think should be typical for a decent package 
as compared to locking a mutex.

for comparison, my getspecific_once and setspecific_once
functions take 155 and 205 nanoseconds/call 
respectively... so getspecific_once should be (on LinuxThreads)
at least 4 times as fast as your StaticMutex
implementation.

Regards,
                                        Owen

====

void 
init_specific ()
{
  tsd_key = pthread_key_create(&tsd_key, NULL);
  tsd_pointer_hash = g_hash_table_new (g_direct_hash, NULL);
}

gpointer
getspecific_once (guint *index)
{
  GArray *array;
  static guint next_index = 0;
  
  array = pthread_getspecific(tsd_key);
  if (!array)
    return NULL;

  if (!*index)
    return NULL;
  /* Since the write to *index is assummed atomic, since
   * *index is nonzero, it must have the correct value.
   */
  else if (*index <= array->len)
    return g_array_index (array, gpointer, (*index - 1));
  else
    return NULL;
}

void
setspecific_once (guint *index, gpointer data)
{
  GArray *array;
  static guint next_index = 0;
  
  array = pthread_getspecific(tsd_key);
  if (!array)
    {
      array = g_array_new (FALSE, FALSE, sizeof(gpointer));
      pthread_setspecific(tsd_key, array);
    }

  if (!*index)
    {
      guint value;
      
      pthread_mutex_lock (&tsd_mutex);
 
      value = GPOINTER_TO_UINT (g_hash_table_lookup(tsd_pointer_hash, index));
      if (!value)
	{
	  value = ++next_index;
	  g_hash_table_insert (tsd_pointer_hash, index,
			       GUINT_TO_POINTER (value));
	}
      *index = value;
      pthread_mutex_unlock (&tsd_mutex);
    }

  if (*index > array->len)
    g_array_set_size (array, *index);

  g_array_index (array, gpointer, (*index - 1)) = data;
}



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