[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
gnulib vs. glib threads
From: |
Bruno Haible |
Subject: |
gnulib vs. glib threads |
Date: |
Sun, 12 Oct 2008 13:49:02 +0200 |
User-agent: |
KMail/1.5.4 |
Here's a comparison between the threads implementations of gnulib
(lib/glthread/*) and GNOME glib (glib-2.12.4/gthread/gthread*),
especially the POSIX and Win32 implementation.
General differences:
- glib is better documented.
- The glib implementation is separated into a platform-independent
part and a platform-dependent part. The two are connected by a vtable
of pointers to the platform-dependent functions. Whereas in gnulib
the dispatching is done through #ifdefs.
- Feature comparison:
- lock/mutex: each has 3 separate data types for simple mutex,
read-write-lock, and recursive mutex. glib also has 'trylock' method.
gnulib has only one method for unlocking a read-write-lock, simpler to
use than glib's different rw_lock_reader_unlock, rw_lock_writer_unlock
methods.
- once-only: same features.
- thread-local storage: glib has two TLS facilities: g_private_* and
g_static_private_*. In the latter, the thread-local value setter
supports an optional destroy function for the old value.
- thread: glib also has priority control, iteration over all threads,
the distinction between joinable and detached threads.
Whereas gnulib has signal mask control on POSIX systems.
- condition variable: same features.
Differences in the implementations for POSIX systems:
- simple lock: glib and gnulib both use a pthread_mutex_t initialized with
default attributes. glib introduces an extra indirection, for static
initializers, whereas gnulib relies on the existence of
PTHREAD_MUTEX_INITIALIZER.
- read-write lock: glib implements its read-write lock as a structure
containing 1 simple lock, 2 condition variables, and a couple of scalar
fields. On platforms without pthread_rwlock_t gnulib does the same; on
platforms with pthread_rwlock_t gnulib uses this type directly
(optionally with extra fields for protecting the initialization when
PTHREAD_RWLOCK_INITIALIZER does not exist).
- recursive lock: glib implements its recursive lock as a structure
containing a simple lock, an owner field, and a depth field. On platforms
without recursive pthread_mutex_t (only old Solaris) gnulib does the same;
on platforms with recursive pthread_mutex_t gnulib uses this type directly
(optionally with extra fields for protecting the initialization when
PTHREAD_RECURSIVE_MUTEX_INITIALIZER[_NP] does not exist).
glib does not check against wraparound of the depth field; gnulib does.
- once-only:
glib implements its once-only control using a global mutex and a global
condition variable. This is inefficient: It prevents two different
once-only controls to be executed simultaneously in different threads.
Whereas gnulib directly uses the pthread_once_t from the system.
- thread-local storage: glib's g_static_private_* TLS consumes a single
TLS key; it stores a pointer to a thread info structure in its value cell;
this structure contains a pointer to an array of values; it manages a
free-list of indices itself.
glib's g_private_* TLS uses the system's TLS directly.
gnulib uses the system's TLS directly.
- thread: glib's thread data type is a pointer to a structure that wraps
a pthread_t. This pointer is stored in TLS.
glib's list of threads is managed as a linear list that causes scalability
problems (due to linear traversal) during thread exit and join.
gnulib uses a pthread_t directly, but assumes that pthread_equal is
equivalent to ==.
- condition variable: glib and gnulib both use the pthread_cond_t directly.
Differences in the implementation for Win32:
- simple lock: glib and gnulib essentially use a CRITICAL_SECTION. So that
it can be used in static initializers, glib introduces an extra
indirection, whereas gnulib adds a spinlock.
- read-write lock: glib implements its read-write lock as a structure
containing 1 simple lock, 2 condition variables, and a couple of scalar
fields. gnulib does it similarly, with self-made waitqueues instead of
condition variables.
- recursive lock: glib implements its recursive lock as a structure
containing a simple lock, an owner field, and a depth field. gnulib
does the same.
glib does not check against wraparound of the depth field; gnulib does.
- once-only:
glib implements its once-only control using a global mutex and a global
condition variable. This is inefficient: It prevents two different
once-only controls to be executed simultaneously in different threads.
Whereas gnulib directly uses essentially only a CRITICAL_SECTION from
the system.
- thread-local storage: glib's g_static_private_* TLS consumes a single
TLS key; it stores a pointer to a thread info structure in its value cell;
this structure contains a pointer to an array of values; it manages a
free-list of indices itself.
glib's g_private_* TLS consumes a single TLS key; it stores an array with
room for 100 pointers in its value cell.
gnulib uses the system's TLS directly.
- thread: glib's and gnulib's thread data type is a pointer to a structure
that wraps a HANDLE to the thread. This pointer is stored in TLS.
glib's list of threads is managed as a linear list that causes scalability
problems (due to linear traversal) during thread exit and join.
At thread creation, glib forces a context switch from the child thread
to the parent thread and back to the child thread, in order to avoid a
race condition (namely, what happens if the child thread calls
thread_self(),
and gets joined by a third thread, before the parent thread had a chance
to store the thread handle in the structure). Additionally, glib serializes
thread creations, so that thread creation becomes a scalability bottleneck.
gnulib avoids the race condition without forcing a context switch.
- condition variable: glib's condition variable type consists of an array
of event objects, one for each waiting thread, and a lock protecting these.
In the timedwait operation, integer overflow can happen in the timeout
conversion. There is also a scalability problem: The signaling operation,
signaling a single thread, is an O(n) operation, where n is the number of
waiting threads.
gnulib's condition variable type consists of a single event object, a few
counters, and a lock protecting these. The scalability problems are even
worse: The signaling operation actually wakes up all waiting threads,
and all but one will go waiting again.
Bruno
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- gnulib vs. glib threads,
Bruno Haible <=