| /* Copyright (C) 2005 Free Software Foundation, Inc. |
| Contributed by Richard Henderson <rth@redhat.com>. |
| |
| This file is part of the GNU OpenMP Library (libgomp). |
| |
| Libgomp 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.1 of the License, or |
| (at your option) any later version. |
| |
| Libgomp 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 libgomp; see the file COPYING.LIB. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
| MA 02110-1301, USA. */ |
| |
| /* As a special exception, if you link this library with other files, some |
| of which are compiled with GCC, to produce an executable, this library |
| does not by itself cause the resulting executable to be covered by the |
| GNU General Public License. This exception does not however invalidate |
| any other reasons why the executable file might be covered by the GNU |
| General Public License. */ |
| |
| /* This file handles the maintainence of threads in response to team |
| creation and termination. */ |
| |
| #include "libgomp.h" |
| #include <stdlib.h> |
| #include <string.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; |
| |
| /* This is the libgomp per-thread data structure. */ |
| #ifdef HAVE_TLS |
| __thread struct gomp_thread gomp_tls_data; |
| #else |
| pthread_key_t gomp_tls_key; |
| #endif |
| |
| |
| /* This structure is used to communicate across pthread_create. */ |
| |
| struct gomp_thread_start_data |
| { |
| struct gomp_team_state ts; |
| void (*fn) (void *); |
| void *fn_data; |
| bool nested; |
| }; |
| |
| |
| /* 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. */ |
| |
| static void * |
| gomp_thread_start (void *xdata) |
| { |
| struct gomp_thread_start_data *data = xdata; |
| struct gomp_thread *thr; |
| void (*local_fn) (void *); |
| void *local_data; |
| |
| #ifdef HAVE_TLS |
| thr = &gomp_tls_data; |
| #else |
| struct gomp_thread local_thr; |
| thr = &local_thr; |
| pthread_setspecific (gomp_tls_key, thr); |
| #endif |
| gomp_sem_init (&thr->release, 0); |
| |
| /* Extract what we need from data. */ |
| local_fn = data->fn; |
| local_data = data->fn_data; |
| thr->ts = data->ts; |
| |
| thr->ts.team->ordered_release[thr->ts.team_id] = &thr->release; |
| |
| if (data->nested) |
| { |
| gomp_barrier_wait (&thr->ts.team->barrier); |
| local_fn (local_data); |
| gomp_barrier_wait (&thr->ts.team->barrier); |
| } |
| else |
| { |
| gomp_threads[thr->ts.team_id] = thr; |
| |
| gomp_barrier_wait (&gomp_threads_dock); |
| do |
| { |
| struct gomp_team *team; |
| |
| local_fn (local_data); |
| |
| /* 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.team_id = 0; |
| thr->ts.work_share_generation = 0; |
| thr->ts.static_trip = 0; |
| |
| gomp_barrier_wait (&team->barrier); |
| gomp_barrier_wait (&gomp_threads_dock); |
| |
| local_fn = thr->fn; |
| local_data = thr->data; |
| } |
| while (local_fn); |
| } |
| |
| return NULL; |
| } |
| |
| |
| /* Create a new team data structure. */ |
| |
| static struct gomp_team * |
| new_team (unsigned nthreads, struct gomp_work_share *work_share) |
| { |
| struct gomp_team *team; |
| size_t size; |
| |
| size = sizeof (*team) + nthreads * sizeof (team->ordered_release[0]); |
| team = gomp_malloc (size); |
| gomp_mutex_init (&team->work_share_lock); |
| |
| team->work_shares = gomp_malloc (4 * sizeof (struct gomp_work_share *)); |
| team->generation_mask = 3; |
| team->oldest_live_gen = work_share == NULL; |
| team->num_live_gen = work_share != NULL; |
| team->work_shares[0] = work_share; |
| |
| team->nthreads = nthreads; |
| gomp_barrier_init (&team->barrier, nthreads); |
| |
| gomp_sem_init (&team->master_release, 0); |
| team->ordered_release[0] = &team->master_release; |
| |
| return team; |
| } |
| |
| |
| /* Free a team data structure. */ |
| |
| static void |
| free_team (struct gomp_team *team) |
| { |
| free (team->work_shares); |
| gomp_mutex_destroy (&team->work_share_lock); |
| gomp_barrier_destroy (&team->barrier); |
| gomp_sem_destroy (&team->master_release); |
| free (team); |
| } |
| |
| |
| /* Launch a team. */ |
| |
| void |
| gomp_team_start (void (*fn) (void *), void *data, unsigned nthreads, |
| struct gomp_work_share *work_share) |
| { |
| struct gomp_thread_start_data *start_data; |
| struct gomp_thread *thr, *nthr; |
| struct gomp_team *team; |
| bool nested; |
| unsigned i, n, old_threads_used = 0; |
| |
| thr = gomp_thread (); |
| nested = thr->ts.team != NULL; |
| |
| team = new_team (nthreads, work_share); |
| |
| /* 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. */ |
| team->prev_ts = thr->ts; |
| |
| thr->ts.team = team; |
| thr->ts.work_share = work_share; |
| thr->ts.team_id = 0; |
| thr->ts.work_share_generation = 0; |
| thr->ts.static_trip = 0; |
| |
| if (nthreads == 1) |
| 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 = work_share; |
| nthr->ts.team_id = i; |
| nthr->ts.work_share_generation = 0; |
| nthr->ts.static_trip = 0; |
| 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 *)); |
| } |
| } |
| |
| start_data = gomp_alloca (sizeof (struct gomp_thread_start_data) |
| * (nthreads-i)); |
| |
| /* Launch new threads. */ |
| for (; i < nthreads; ++i, ++start_data) |
| { |
| pthread_t pt; |
| int err; |
| |
| start_data->ts.team = team; |
| start_data->ts.work_share = work_share; |
| start_data->ts.team_id = i; |
| start_data->ts.work_share_generation = 0; |
| start_data->ts.static_trip = 0; |
| start_data->fn = fn; |
| start_data->fn_data = data; |
| start_data->nested = nested; |
| |
| err = pthread_create (&pt, &gomp_thread_attr, |
| gomp_thread_start, start_data); |
| if (err != 0) |
| gomp_fatal ("Thread creation failed: %s", strerror (err)); |
| } |
| |
| 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 (nthreads < old_threads_used) |
| gomp_barrier_reinit (&gomp_threads_dock, nthreads); |
| } |
| |
| |
| /* 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; |
| |
| gomp_barrier_wait (&team->barrier); |
| |
| thr->ts = team->prev_ts; |
| |
| free_team (team); |
| } |
| |
| |
| /* Constructors for this file. */ |
| |
| static void __attribute__((constructor)) |
| initialize_team (void) |
| { |
| struct gomp_thread *thr; |
| |
| #ifndef HAVE_TLS |
| static struct gomp_thread initial_thread_tls_data; |
| |
| pthread_key_create (&gomp_tls_key, NULL); |
| pthread_setspecific (gomp_tls_key, &initial_thread_tls_data); |
| #endif |
| |
| #ifdef HAVE_TLS |
| thr = &gomp_tls_data; |
| #else |
| thr = &initial_thread_tls_data; |
| #endif |
| gomp_sem_init (&thr->release, 0); |
| } |