This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
[PATCH][libgomp/gomp-3_0-branch] Support for OpenMP in user threads, and pooling for nested threads
- From: Johannes Singler <singler at ira dot uka dot de>
- To: gcc-patches at gcc dot gnu dot org, Jakub Jelinek <jakub at redhat dot com>
- Cc: Jakob Blomer <Jakob dot Blomer at ira dot uka dot de>
- Date: Thu, 24 Apr 2008 15:31:22 +0200
- Subject: [PATCH][libgomp/gomp-3_0-branch] Support for OpenMP in user threads, and pooling for nested threads
This patch for gomp-3_0-branch enables the following functionality.
-OpenMP parallel regions in multiple concurrent user (p)threads.
-Thread pooling for nested parallel regions.
Details:
-The code is simplified since nested threads are not treated differently
from top-level threads.
-A pool thread waits at only exactly one barrier between invocations.
-One thread pool is held per user thread. This allows very efficient
allocation of threads. Since the pool cannot be deallocated at the end
of a user thread (determination of end not safely possible), the pools
are either recycled or garbage collected when allocating a new one.
Thus, the number of (inactive) pool threads stays bounded by the number
of OpenMP threads in concurrently working user threads.
-By the above approach, threadprivate semantics are guaranteed.
-The "processor" overload heuristic for blocking threads does not count
the idle pool threads.
Performance according to the CLOMP benchmark:
3gomp-newthreadpool (our patch):
./clomp_xgcc_i10pc121 8 1 64 100 32 1 100, calc_deposit, OMP Barrier,
Serial Ref, Bestcase OMP, Static OMP, Dynamic OMP, Manual OMP
Runtime, 0.0117879, 0.172061, 4.00551, 0.544991, 1.58542, 10.7785, 1.11315
us/Loop, 0.0754425, 1.10119, 25.6353, 3.48794, 10.1467, 68.9825, 7.12416
Speedup, N/A, N/A, 1, 7.34968, 2.52646, 0.37162, 3.59836
Efficacy, N/A, N/A, N/A, 100.00%, 34.38%, 5.06%, 48.96%
Overhead, N/A, N/A, N/A, 0.00, 6.66, 65.49, 3.64
3gomp:
./clomp_gcc_i10pc121 8 1 64 100 32 1 100, calc_deposit, OMP Barrier,
Serial Ref, Bestcase OMP, Static OMP, Dynamic OMP, Manual OMP
Runtime, 0.0117931, 0.159399, 4.0048, 0.665463, 1.50918, 11.2974, 1.41252
us/Loop, 0.0754761, 1.02015, 25.6307, 4.25896, 9.65873, 72.3033, 9.04011
Speedup, N/A, N/A, 1, 6.01806, 2.65363, 0.354488, 2.83522
Efficacy, N/A, N/A, N/A, 100.00%, 44.09%, 5.89%, 47.11%
Overhead, N/A, N/A, N/A, 0.00, 5.40, 68.04, 4.78
Performance according to the EPCC microbenchmark:
-The parallel construct gets 27% slower (3,2->4,4 microseconds).
However, this is still *much* faster (factors) than the current trunk
implementation.
-All other constructs remain unchanged.
Code contributed by Jakob Blomer (jakob.blomer@ira.uka.de), supervised
by Johannes Singler (singler@ira.uka.de).
Tested x86_64-unknown-linux-gnu: No regressions
i386-apple-darwin9.2.2: No regressions
i686-pc-linux-gnu: No regressions
Please comment and/or approve.
2008-04-24 Jakob Blomer <jakob.blomer@ira.uka.de>
* team.c: Thread-safe thread pool,
thread pool for nested threads.
(gomp_team_start) Create thread pool on demand
for user-created pthreads,
garbage collection of thread pool of terminated
pthreads,
barrier at end of function eliminated,
gomp_managed_threads does not take idle pool
threads into account
(gomp_team_end) Put worker threads back into pool
gomp.
(gomp_new_team) Allocate memory for worker threads
array, will be automatically freed in free_team.
(initialize_team) Initialize global user pthread
directory.
New static functions: gomp_new_thread_pool,
gomp_free_thread_pool
* libgomp.h: Thread-pool data structure,
new pointer in struct gomp_thread to thread pool.
* $arch/mutex.h:
New function gomp_init_mutex_locked
* $arch/sem.h:
New synchronization primitive signal, which is a
binary semaphore resp. a wrapper for mutex.
New inlined functions: gomp_signal_init,
gomp_signal_wait, gomp_signal_post,
gomp_signal_destroy.
Jakob & Johannes
Index: team.c
===================================================================
--- team.c (Revision 122)
+++ team.c (Arbeitskopie)
@@ -31,18 +31,21 @@
#include "libgomp.h"
#include <stdlib.h>
#include <string.h>
+#include <signal.h>
+#include <errno.h>
-/* This array manages threads spawned from the top level, which will
- return to the idle loop once the current PARALLEL construct ends. */
-static struct gomp_thread **gomp_threads;
-static unsigned gomp_threads_size;
-static unsigned gomp_threads_used;
-
/* This attribute contains PTHREAD_CREATE_DETACHED. */
pthread_attr_t gomp_thread_attr;
-/* This barrier holds and releases threads waiting in gomp_threads. */
-static gomp_barrier_t gomp_threads_dock;
+struct user_pthread_info
+{
+ pthread_t thread;
+ struct gomp_thread_pool *pool;
+};
+/* Global directory of user pthreads with a OpenMP thread pool */
+unsigned num_user_pthreads = 0;
+struct user_pthread_info *user_pthreads = NULL;
+gomp_mutex_t user_pthreads_lock;
/* This is the libgomp per-thread data structure. */
#ifdef HAVE_TLS
@@ -60,10 +63,11 @@
void *fn_data;
struct gomp_team_state ts;
struct gomp_task *task;
+ struct gomp_thread_pool *thread_pool;
bool nested;
+ gomp_signal_t thread_created;
};
-
/* This function is a pthread_create entry point. This contains the idle
loop in which a thread waits to be called up to become part of a team. */
@@ -83,54 +87,39 @@
pthread_setspecific (gomp_tls_key, thr);
#endif
gomp_sem_init (&thr->release, 0);
+ /* event is fired when threads are to be woken up */
+ gomp_signal_init (&thr->pool_release, 0);
/* Extract what we need from data. */
- local_fn = data->fn;
- local_data = data->fn_data;
+ thr->fn = data->fn;
+ thr->data = data->fn_data;
thr->ts = data->ts;
thr->task = data->task;
+ thr->thread_pool = data->thread_pool;
thr->ts.team->ordered_release[thr->ts.team_id] = &thr->release;
+ thr->ts.team->workers[thr->ts.team_id - 1] = thr;
- if (data->nested)
+ gomp_signal_post (&data->thread_created);
+ /* Loop as long as thread is in the thread pool.
+ Thread pool is deallocated by garbage collection (gomp_team_start). */
+ do
{
- gomp_barrier_wait (&thr->ts.team->barrier);
+ /* wait until woken up */
+ gomp_signal_wait (&thr->pool_release);
+
+ local_fn = thr->fn;
+ if (__builtin_expect(local_fn == NULL, 0))
+ break;
+ local_data = thr->data;
+ /* Execute OMP parallel region (user code). */
local_fn (local_data);
- gomp_barrier_wait_last (&thr->ts.team->barrier);
- }
- else
- {
- gomp_threads[thr->ts.team_id] = thr;
+ gomp_end_task ();
- gomp_barrier_wait (&gomp_threads_dock);
- do
- {
- struct gomp_team *team;
+ thr->fn = NULL;
- local_fn (local_data);
- gomp_end_task ();
-
- /* Clear out the team and function data. This is a debugging
- signal that we're in fact back in the dock. */
- team = thr->ts.team;
- thr->fn = NULL;
- thr->data = NULL;
- thr->ts.team = NULL;
- thr->ts.work_share = NULL;
- thr->ts.last_work_share = NULL;
- thr->ts.team_id = 0;
- thr->ts.level = 0;
- thr->ts.active_level = 0;
-
- gomp_barrier_wait_last (&team->barrier);
- gomp_barrier_wait (&gomp_threads_dock);
-
- local_fn = thr->fn;
- local_data = thr->data;
- }
- while (local_fn);
- }
-
+ gomp_barrier_wait_last (&thr->ts.team->barrier);
+ } while (local_fn);
return NULL;
}
@@ -145,7 +134,8 @@
int i;
size = sizeof (*team) + nthreads * (sizeof (team->ordered_release[0])
- + sizeof (team->implicit_task[0]));
+ + sizeof (team->implicit_task[0])
+ + sizeof (team->workers[0]));
team = gomp_malloc (size);
team->work_share_chunk = 8;
@@ -169,6 +159,8 @@
team->ordered_release = (void *) &team->implicit_task[nthreads];
team->ordered_release[0] = &team->master_release;
+ team->workers = (void *) &team->ordered_release[nthreads];
+
return team;
}
@@ -198,6 +190,37 @@
}
+/* Allocate and initialize thread pool. */
+
+static struct gomp_thread_pool
+*gomp_new_thread_pool (void)
+{
+ struct gomp_thread_pool *pool =
+ gomp_malloc (sizeof(struct gomp_thread_pool));
+
+ gomp_mutex_init (&pool->lock);
+ pool->threads = gomp_malloc (sizeof(void *));
+ pool->size = 1;
+ pool->capacity = 1;
+
+ return pool;
+}
+
+/* Terminate pool threads and free thread pool. */
+static void
+gomp_free_thread_pool (struct gomp_thread_pool *pool)
+{
+ int i;
+
+ for (i = 1; i < pool->size; i++)
+ {
+ gomp_signal_post (&pool->threads[i]->pool_release);
+ }
+ gomp_mutex_destroy (&pool->lock);
+ free (pool->threads);
+ free (pool);
+}
+
/* Launch a team. */
void
@@ -208,15 +231,77 @@
struct gomp_thread *thr, *nthr;
struct gomp_task *task;
struct gomp_task_icv *icv;
+ struct gomp_thread_pool *thread_pool;
bool nested;
- unsigned i, n, old_threads_used = 0;
+ unsigned i, n;
pthread_attr_t thread_attr, *attr;
thr = gomp_thread ();
nested = thr->ts.team != NULL;
task = thr->task;
icv = task ? &task->icv : &gomp_global_icv;
+ /* Check whether a thread pool for current user pthread has to be created.
+ I. e., is this the first parallel region within a new user pthread? */
+ if (__builtin_expect(thr->thread_pool == NULL, 0))
+ {
+ /* Garbage collection */
+ int i;
+ int freed_threads = 0;
+ gomp_mutex_lock (&user_pthreads_lock);
+ for (i = 0; i < num_user_pthreads; i++)
+ {
+ /* Is this pthread re-used? */
+ if (pthread_equal (pthread_self (), user_pthreads[i].thread))
+ {
+ /* perfect, we take its thread pool */
+ thr->thread_pool = user_pthreads[i].pool;
+ }
+ /* Do we have a thread pool whose user pthread is non-existent? */
+ if (pthread_kill (user_pthreads[i].thread, 0) == ESRCH)
+ {
+ /* clean up and mark */
+ gomp_free_thread_pool (user_pthreads[i].pool);
+ user_pthreads[i].pool = NULL;
+ freed_threads++;
+ }
+ }
+
+ /* Compress thread-pool directory */
+ if (freed_threads > 0)
+ {
+ struct user_pthread_info *new_user_pthreads = gomp_malloc (
+ (num_user_pthreads - freed_threads) * sizeof(user_pthreads[0]));
+ int new_i = 0;
+ for (i = 0; i < num_user_pthreads; i++)
+ {
+ if (user_pthreads[i].pool != NULL)
+ new_user_pthreads[new_i++] = user_pthreads[i];
+ }
+ free (user_pthreads);
+ user_pthreads = new_user_pthreads;
+ num_user_pthreads -= freed_threads;
+ }
+
+ /* Do we have do create a new pool? If yes, this thread was not re-used
+ by the system. */
+ if (thr->thread_pool == NULL)
+ {
+ thr->thread_pool = gomp_new_thread_pool ();
+
+ /* append to global thread pool directory */
+ if (num_user_pthreads++ == 0)
+ user_pthreads = gomp_malloc (sizeof(struct user_pthread_info));
+ else
+ user_pthreads = gomp_realloc (user_pthreads,
+ num_user_pthreads * sizeof(struct user_pthread_info));
+ user_pthreads[num_user_pthreads-1].thread = pthread_self ();
+ user_pthreads[num_user_pthreads-1].pool = thr->thread_pool;
+ }
+ gomp_mutex_unlock (&user_pthreads_lock);
+ }
+ thread_pool = thr->thread_pool;
+
/* Always save the previous state, even if this isn't a nested team.
In particular, we should save any work share state from an outer
orphaned work share construct. */
@@ -240,89 +325,42 @@
return;
i = 1;
-
- /* We only allow the reuse of idle threads for non-nested PARALLEL
- regions. This appears to be implied by the semantics of
- threadprivate variables, but perhaps that's reading too much into
- things. Certainly it does prevent any locking problems, since
- only the initial program thread will modify gomp_threads. */
- if (!nested)
- {
- old_threads_used = gomp_threads_used;
-
- if (nthreads <= old_threads_used)
- n = nthreads;
- else if (old_threads_used == 0)
- {
- n = 0;
- gomp_barrier_init (&gomp_threads_dock, nthreads);
- }
- else
- {
- n = old_threads_used;
-
- /* Increase the barrier threshold to make sure all new
- threads arrive before the team is released. */
- gomp_barrier_reinit (&gomp_threads_dock, nthreads);
- }
-
- /* Not true yet, but soon will be. We're going to release all
- threads from the dock, and those that aren't part of the
- team will exit. */
- gomp_threads_used = nthreads;
-
- /* Release existing idle threads. */
- for (; i < n; ++i)
- {
- nthr = gomp_threads[i];
- nthr->ts.team = team;
- nthr->ts.work_share = &team->work_shares[0];
- nthr->ts.last_work_share = NULL;
- nthr->ts.team_id = i;
- nthr->ts.level = team->prev_ts.level + 1;
- nthr->ts.active_level = thr->ts.active_level;
#ifdef HAVE_SYNC_BUILTINS
- nthr->ts.single_count = 0;
+ __sync_fetch_and_add (&gomp_managed_threads, nthreads-1L);
+#else
+ gomp_mutex_lock (&gomp_remaining_threads_lock);
+ gomp_managed_threads += nthreads-1;
+ gomp_mutex_unlock (&gomp_remaining_threads_lock);
#endif
- nthr->ts.static_trip = 0;
- nthr->task = &team->implicit_task[i];
- gomp_init_task (nthr->task, task, icv);
- nthr->fn = fn;
- nthr->data = data;
- team->ordered_release[i] = &nthr->release;
- }
- if (i == nthreads)
- goto do_release;
-
- /* If necessary, expand the size of the gomp_threads array. It is
- expected that changes in the number of threads is rare, thus we
- make no effort to expand gomp_threads_size geometrically. */
- if (nthreads >= gomp_threads_size)
- {
- gomp_threads_size = nthreads + 1;
- gomp_threads
- = gomp_realloc (gomp_threads,
- gomp_threads_size
- * sizeof (struct gomp_thread_data *));
- }
- }
-
- if (__builtin_expect (nthreads > old_threads_used, 0))
+ struct gomp_thread **workers = team->workers;
+ gomp_mutex_lock (&thread_pool->lock);
+ int pool_size = thread_pool->size;
+ n = (nthreads < pool_size) ? nthreads : pool_size;
+ for (; i < n; ++i)
{
- long diff = (long) nthreads - (long) old_threads_used;
-
- if (old_threads_used == 0)
- --diff;
-
+ nthr = thread_pool->threads[pool_size-i];
+
+ nthr->thread_pool = thread_pool;
+ workers[i-1] = nthr;
+ nthr->ts.team = team;
+ nthr->ts.work_share = &team->work_shares[0];
+ nthr->ts.last_work_share = NULL;
+ nthr->ts.team_id = i;
+ nthr->ts.level = team->prev_ts.level + 1;
+ nthr->ts.active_level = thr->ts.active_level;
#ifdef HAVE_SYNC_BUILTINS
- __sync_fetch_and_add (&gomp_managed_threads, diff);
-#else
- gomp_mutex_lock (&gomp_remaining_threads_lock);
- gomp_managed_threads += diff;
- gomp_mutex_unlock (&gomp_remaining_threads_lock);
+ nthr->ts.single_count = 0;
#endif
+ nthr->ts.static_trip = 0;
+ nthr->task = &team->implicit_task[i];
+ gomp_init_task (nthr->task, task, icv);
+ nthr->fn = fn;
+ nthr->data = data;
+ team->ordered_release[i] = &nthr->release;
}
+ thread_pool->size -= n-1;
+ gomp_mutex_unlock (&thread_pool->lock);
attr = &gomp_thread_attr;
if (__builtin_expect (gomp_cpu_affinity != NULL, 0))
@@ -336,14 +374,16 @@
}
start_data = gomp_alloca (sizeof (struct gomp_thread_start_data)
- * (nthreads-i));
-
+ * (nthreads-i));
/* Launch new threads. */
- for (; i < nthreads; ++i, ++start_data)
+ for (; i < nthreads; ++i,++start_data)
{
pthread_t pt;
int err;
+ gomp_signal_init (&start_data->thread_created, 0);
+ start_data->thread_pool = thread_pool;
+ /* team->workers will be set in gomp_thread_start */
start_data->fn = fn;
start_data->fn_data = data;
start_data->ts.team = team;
@@ -366,43 +406,28 @@
err = pthread_create (&pt, attr, gomp_thread_start, start_data);
if (err != 0)
gomp_fatal ("Thread creation failed: %s", strerror (err));
+ gomp_signal_wait (&start_data->thread_created);
+ gomp_signal_destroy (&start_data->thread_created);
}
+ /* Wake up worker threads. */
+ for (i = 0; i < nthreads-1; ++i)
+ gomp_signal_post (&workers[i]->pool_release);
+
if (__builtin_expect (gomp_cpu_affinity != NULL, 0))
pthread_attr_destroy (&thread_attr);
-
- do_release:
- gomp_barrier_wait (nested ? &team->barrier : &gomp_threads_dock);
-
- /* Decrease the barrier threshold to match the number of threads
- that should arrive back at the end of this team. The extra
- threads should be exiting. Note that we arrange for this test
- to never be true for nested teams. */
- if (__builtin_expect (nthreads < old_threads_used, 0))
- {
- long diff = (long) nthreads - (long) old_threads_used;
-
- gomp_barrier_reinit (&gomp_threads_dock, nthreads);
-
-#ifdef HAVE_SYNC_BUILTINS
- __sync_fetch_and_add (&gomp_managed_threads, diff);
-#else
- gomp_mutex_lock (&gomp_remaining_threads_lock);
- gomp_managed_threads += diff;
- gomp_mutex_unlock (&gomp_remaining_threads_lock);
-#endif
- }
}
/* Terminate the current team. This is only to be called by the master
thread. We assume that we must wait for the other threads. */
-
void
gomp_team_end (void)
{
struct gomp_thread *thr = gomp_thread ();
struct gomp_team *team = thr->ts.team;
+ struct gomp_thread_pool *thread_pool = thr->thread_pool;
+ int num_workers = team->nthreads - 1;
gomp_barrier_wait (&team->barrier);
@@ -411,21 +436,43 @@
gomp_end_task ();
thr->ts = team->prev_ts;
- if (__builtin_expect (thr->ts.team != NULL, 0))
+ if (__builtin_expect(num_workers > 0, 1))
{
#ifdef HAVE_SYNC_BUILTINS
- __sync_fetch_and_add (&gomp_managed_threads, 1L - team->nthreads);
+ __sync_fetch_and_add (&gomp_managed_threads, -num_workers);
#else
gomp_mutex_lock (&gomp_remaining_threads_lock);
- gomp_managed_threads -= team->nthreads - 1L;
+ gomp_managed_threads -= num_workers;
gomp_mutex_unlock (&gomp_remaining_threads_lock);
#endif
+
+ int i;
+ struct gomp_thread **workers = team->workers;
+
+ gomp_mutex_lock (&thread_pool->lock);
+ if (__builtin_expect((1 + thread_pool->size + num_workers) >
+ thread_pool->capacity, 0))
+ {
+ thread_pool->capacity = 1 + thread_pool->size + num_workers;
+ thread_pool->threads = gomp_realloc (thread_pool->threads,
+ thread_pool->capacity * sizeof(struct gomp_thread *));
+ }
+ int size = thread_pool->size;
+ struct gomp_thread **threads = thread_pool->threads;
+ /* Put the workers back int the thread pool. */
+ for (i = num_workers-1; i >= 0; i--)
+ threads[size++] = workers[i];
+ thread_pool->size += num_workers;
+ gomp_mutex_unlock (&thread_pool->lock);
}
+
+ thr->ts = team->prev_ts;
free_team (team);
}
+
/* Constructors for this file. */
static void __attribute__((constructor))
@@ -446,4 +493,5 @@
thr = &initial_thread_tls_data;
#endif
gomp_sem_init (&thr->release, 0);
+ gomp_mutex_init (&user_pthreads_lock);
}
Index: libgomp.h
===================================================================
--- libgomp.h (Revision 122)
+++ libgomp.h (Arbeitskopie)
@@ -248,6 +248,10 @@
parallels, as the master is a member of two teams. */
gomp_sem_t master_release;
+ /* This array contains all the worker threads to be able to put them back
+ into the pool. */
+ struct gomp_thread **workers;
+
/* This points to an array with pointers to the release semaphore
of the threads in the team. */
gomp_sem_t **ordered_release;
@@ -303,8 +307,22 @@
/* This semaphore is used for ordered loops. */
gomp_sem_t release;
+
+ /* This points to the respective thread pool */
+ struct gomp_thread_pool *thread_pool;
+
+ /* This signal is to wake up a thread in the thread pool */
+ gomp_signal_t pool_release;
};
+struct gomp_thread_pool
+{
+ struct gomp_thread **threads;
+ int size;
+ int capacity;
+ gomp_mutex_t lock;
+};
+
/* ... and here is that TLS data. */
#ifdef HAVE_TLS
Index: config/linux/mutex.h
===================================================================
--- config/linux/mutex.h (Revision 122)
+++ config/linux/mutex.h (Arbeitskopie)
@@ -41,6 +41,11 @@
*mutex = 0;
}
+static inline void gomp_mutex_init_locked (gomp_mutex_t *mutex)
+{
+ *mutex = 1;
+}
+
extern void gomp_mutex_lock_slow (gomp_mutex_t *mutex);
static inline void gomp_mutex_lock (gomp_mutex_t *mutex)
{
Index: config/linux/sem.h
===================================================================
--- config/linux/sem.h (Revision 122)
+++ config/linux/sem.h (Arbeitskopie)
@@ -32,8 +32,36 @@
#ifndef GOMP_SEM_H
#define GOMP_SEM_H 1
+#include "mutex.h"
+
typedef int gomp_sem_t;
+/* This is a binary semaphore aka signal,
+ technically just a wrapper for a mutex */
+typedef gomp_mutex_t gomp_signal_t;
+static inline void gomp_signal_init (gomp_signal_t *signal, int fired)
+{
+ if (fired == 0)
+ gomp_mutex_init_locked (signal);
+ else
+ gomp_mutex_init (signal);
+}
+
+static inline void gomp_signal_wait (gomp_signal_t *signal)
+{
+ gomp_mutex_lock (signal);
+}
+
+static inline void gomp_signal_post (gomp_signal_t *signal)
+{
+ gomp_mutex_unlock (signal);
+}
+
+static inline void gomp_signal_destroy (gomp_signal_t *signal)
+{
+ gomp_mutex_destroy (signal);
+}
+
static inline void gomp_sem_init (gomp_sem_t *sem, int value)
{
*sem = value;
Index: config/posix/mutex.h
===================================================================
--- config/posix/mutex.h (Revision 122)
+++ config/posix/mutex.h (Arbeitskopie)
@@ -47,6 +47,12 @@
pthread_mutex_lock (mutex);
}
+static inline void gomp_mutex_init_locked (gomp_mutex_t *mutex)
+{
+ gomp_mutex_init (mutex);
+ gomp_mutex_lock (mutex);
+}
+
static inline void gomp_mutex_unlock (gomp_mutex_t *mutex)
{
pthread_mutex_unlock (mutex);
Index: config/posix/sem.h
===================================================================
--- config/posix/sem.h (Revision 122)
+++ config/posix/sem.h (Arbeitskopie)
@@ -49,6 +49,35 @@
#ifdef HAVE_BROKEN_POSIX_SEMAPHORES
#include <pthread.h>
+#include "mutex.h"
+
+/* This is a binary semaphore aka signal,
+ technically just a wrapper for a mutex */
+typedef gomp_mutex_t gomp_signal_t;
+
+static inline void gomp_signal_init (gomp_signal_t *signal, int fired)
+{
+ if (fired == 0)
+ gomp_mutex_init_locked (signal);
+ else
+ gomp_mutex_init (signal);
+}
+
+static inline void gomp_signal_wait (gomp_signal_t *signal)
+{
+ gomp_mutex_lock (signal);
+}
+
+static inline void gomp_signal_post (gomp_signal_t *signal)
+{
+ gomp_mutex_unlock (signal);
+}
+
+static inline void gomp_signal_destroy (gomp_signal_t *signal)
+{
+ gomp_mutex_destroy (signal);
+}
+
struct gomp_sem
{
pthread_mutex_t mutex;