mirror of
https://https.git.savannah.gnu.org/git/gnulib.git
synced 2026-06-15 15:25:49 +00:00
460 lines
11 KiB
C
460 lines
11 KiB
C
/* Creating and controlling ISO C 11 threads.
|
|
Copyright (C) 2005-2026 Free Software Foundation, Inc.
|
|
|
|
This file 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.
|
|
|
|
This file 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 this program. If not, see <https://www.gnu.org/licenses/>. */
|
|
|
|
/* Written by Bruno Haible <bruno@clisp.org>, 2005, 2019.
|
|
Based on GCC's gthr-posix.h, gthr-posix95.h, gthr-win32.h. */
|
|
|
|
#include <config.h>
|
|
|
|
#include <threads.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#if HAVE_THREADS_H
|
|
/* Provide workarounds. */
|
|
|
|
# if BROKEN_THRD_START_T || BROKEN_THRD_JOIN
|
|
|
|
# undef thrd_t
|
|
|
|
/* AIX 7.1..7.2 defines thrd_start_t incorrectly, namely as
|
|
'void * (*) (void *)' instead of 'int (*) (void *)'.
|
|
As a consequence, its thrd_join function never stores an exit code.
|
|
AIX 7.3.1 has a corrected thrd_start_t. But the thrd_join function still
|
|
never stores an exit code. */
|
|
|
|
/* The Thread-Specific Storage (TSS) key that allows accessing each thread's
|
|
'struct thrd_with_exitcode *' pointer. */
|
|
static tss_t thrd_with_exitcode_key;
|
|
|
|
/* Initializes thrd_with_exitcode_key.
|
|
This function must only be called once. */
|
|
static void
|
|
do_init_thrd_with_exitcode_key (void)
|
|
{
|
|
if (tss_create (&thrd_with_exitcode_key, NULL) != thrd_success)
|
|
abort ();
|
|
}
|
|
|
|
/* Initializes thrd_with_exitcode_key. */
|
|
static void
|
|
init_thrd_with_exitcode_key (void)
|
|
{
|
|
static once_flag once = ONCE_FLAG_INIT;
|
|
call_once (&once, do_init_thrd_with_exitcode_key);
|
|
}
|
|
|
|
typedef union
|
|
{
|
|
struct thrd_with_exitcode t;
|
|
struct
|
|
{
|
|
thrd_t tid; /* reserve memory for t.tid */
|
|
int detached; /* reserve memory for t.detached */
|
|
thrd_start_t mainfunc;
|
|
void *arg;
|
|
} a;
|
|
}
|
|
main_arg_t;
|
|
|
|
static
|
|
# if BROKEN_THRD_START_T
|
|
void *
|
|
# else /* BROKEN_THRD_JOIN */
|
|
int
|
|
# endif
|
|
thrd_main_func (void *pmarg)
|
|
{
|
|
/* Unpack the object that combines mainfunc and arg. */
|
|
main_arg_t *main_arg = (main_arg_t *) pmarg;
|
|
thrd_start_t mainfunc = main_arg->a.mainfunc;
|
|
void *arg = main_arg->a.arg;
|
|
|
|
if (tss_set (thrd_with_exitcode_key, &main_arg->t) != thrd_success)
|
|
abort ();
|
|
|
|
/* Execute mainfunc, with arg as argument. */
|
|
{
|
|
int exitcode = mainfunc (arg);
|
|
/* Store the exitcode, for use by thrd_join(). */
|
|
main_arg->t.exitcode = exitcode;
|
|
if (main_arg->t.detached)
|
|
{
|
|
/* Clean up the thread, like thrd_join would do. */
|
|
free (&main_arg->t);
|
|
}
|
|
# if BROKEN_THRD_START_T
|
|
return NULL;
|
|
# else /* BROKEN_THRD_JOIN */
|
|
return 0;
|
|
# endif
|
|
}
|
|
}
|
|
|
|
int
|
|
rpl_thrd_create (rpl_thrd_t *threadp, thrd_start_t mainfunc, void *arg)
|
|
# undef thrd_create
|
|
{
|
|
init_thrd_with_exitcode_key ();
|
|
{
|
|
/* Combine mainfunc and arg in a single object.
|
|
A stack-allocated object does not work, because it would be out of
|
|
existence when thrd_create returns before pthread_main_func is
|
|
entered. So, allocate it in the heap. */
|
|
main_arg_t *main_arg = (main_arg_t *) malloc (sizeof (main_arg_t));
|
|
if (main_arg == NULL)
|
|
return thrd_nomem;
|
|
main_arg->a.mainfunc = mainfunc;
|
|
main_arg->a.arg = arg;
|
|
main_arg->t.detached = 0;
|
|
{
|
|
int err =
|
|
thrd_create ((thrd_t *) &main_arg->t.tid, thrd_main_func, main_arg);
|
|
if (err == thrd_success)
|
|
*threadp = &main_arg->t;
|
|
else
|
|
free (main_arg);
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
rpl_thrd_t
|
|
rpl_thrd_current (void)
|
|
# undef thrd_current
|
|
{
|
|
init_thrd_with_exitcode_key ();
|
|
{
|
|
rpl_thrd_t thread =
|
|
(struct thrd_with_exitcode *) tss_get (thrd_with_exitcode_key);
|
|
if (thread == NULL)
|
|
{
|
|
/* This happens only in threads that have not been created through
|
|
thrd_create(), such as the main thread. */
|
|
for (;;)
|
|
{
|
|
thread =
|
|
(struct thrd_with_exitcode *)
|
|
malloc (sizeof (struct thrd_with_exitcode));
|
|
if (thread != NULL)
|
|
break;
|
|
/* Memory allocation failed. There is not much we can do. Have to
|
|
busy-loop, waiting for the availability of memory. */
|
|
{
|
|
struct timespec ts =
|
|
{
|
|
.tv_sec = 1,
|
|
.tv_nsec = 0
|
|
};
|
|
thrd_sleep (&ts, NULL);
|
|
}
|
|
}
|
|
thread->tid = thrd_current ();
|
|
thread->detached = 0; /* This can lead to a memory leak. */
|
|
thread->exitcode = 0; /* just to be deterministic */
|
|
if (tss_set (thrd_with_exitcode_key, thread) != thrd_success)
|
|
abort ();
|
|
}
|
|
return thread;
|
|
}
|
|
}
|
|
|
|
int
|
|
rpl_thrd_equal (rpl_thrd_t thread1, rpl_thrd_t thread2)
|
|
{
|
|
return thread1 == thread2;
|
|
}
|
|
|
|
int
|
|
rpl_thrd_detach (rpl_thrd_t thread)
|
|
# undef thrd_detach
|
|
{
|
|
if (thread->detached)
|
|
return thrd_error;
|
|
{
|
|
int err =
|
|
thrd_detach (thread == rpl_thrd_current ()
|
|
? /* thread->tid may not be initialized at this point. */
|
|
thrd_current ()
|
|
: thread->tid);
|
|
if (err == thrd_success)
|
|
thread->detached = 1;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
int
|
|
rpl_thrd_join (rpl_thrd_t thread, int *exitcodep)
|
|
# undef thrd_join
|
|
{
|
|
if (thread == rpl_thrd_current () || thread->detached)
|
|
return thrd_error;
|
|
{
|
|
int err = thrd_join (thread->tid, NULL);
|
|
if (err == thrd_success)
|
|
{
|
|
if (exitcodep != NULL)
|
|
*exitcodep = thread->exitcode;
|
|
free (thread);
|
|
}
|
|
return err;
|
|
}
|
|
}
|
|
|
|
_Noreturn void
|
|
rpl_thrd_exit (int exitcode)
|
|
{
|
|
rpl_thrd_t t = rpl_thrd_current ();
|
|
|
|
/* Store the exitcode, for use by thrd_join(). */
|
|
t->exitcode = exitcode;
|
|
if (t->detached)
|
|
{
|
|
/* Clean up the thread, like thrd_join would do. */
|
|
free (t);
|
|
}
|
|
pthread_exit (NULL);
|
|
}
|
|
|
|
# endif
|
|
|
|
# if BROKEN_THRD_JOIN_NULL
|
|
|
|
/* On Solaris 11.4, thrd_join crashes when the second argument is NULL. */
|
|
int
|
|
rpl_thrd_join (thrd_t thread, int *exitcodep)
|
|
# undef thrd_join
|
|
{
|
|
int exitcode;
|
|
int err = thrd_join (thread, &exitcode);
|
|
if (err == 0 && exitcodep != NULL)
|
|
*exitcodep = exitcode;
|
|
return err;
|
|
}
|
|
|
|
# endif
|
|
|
|
#else
|
|
|
|
# include <errno.h>
|
|
# include <stdint.h>
|
|
|
|
# if defined _WIN32 && ! defined __CYGWIN__
|
|
/* Use Windows threads. */
|
|
|
|
# define WIN32_LEAN_AND_MEAN /* avoid including junk */
|
|
# include <windows.h>
|
|
|
|
# else
|
|
/* Use POSIX threads. */
|
|
|
|
# include <pthread.h>
|
|
# include <sched.h>
|
|
|
|
# endif
|
|
|
|
/* The main functions passed to thrd_create and
|
|
pthread_create/glwthread_thread_create have different return types:
|
|
'int' vs. 'void *'. */
|
|
|
|
struct pthread_main_arg_t
|
|
{
|
|
thrd_start_t mainfunc;
|
|
void *arg;
|
|
};
|
|
|
|
static void *
|
|
pthread_main_func (void *pmarg)
|
|
{
|
|
/* Unpack the object that combines mainfunc and arg. */
|
|
struct pthread_main_arg_t *pthread_main_arg =
|
|
(struct pthread_main_arg_t *) pmarg;
|
|
thrd_start_t mainfunc = pthread_main_arg->mainfunc;
|
|
void *arg = pthread_main_arg->arg;
|
|
|
|
/* Free it. */
|
|
free (pmarg);
|
|
|
|
/* Execute mainfunc, with arg as argument. */
|
|
{
|
|
int exitcode = mainfunc (arg);
|
|
/* Note: When using Windows threads, this exit code is different from the
|
|
argument passed to ExitThread(), because the latter should never be 259,
|
|
see <https://docs.microsoft.com/de-de/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodethread>,
|
|
whereas the exit code passed to thrd_exit() is not constrained. */
|
|
return (void *) (intptr_t) exitcode;
|
|
}
|
|
}
|
|
|
|
# if defined _WIN32 && ! defined __CYGWIN__
|
|
/* Use Windows threads. */
|
|
|
|
int
|
|
thrd_create (thrd_t *threadp, thrd_start_t mainfunc, void *arg)
|
|
{
|
|
/* Combine mainfunc and arg in a single object.
|
|
A stack-allocated object does not work, because it would be out of
|
|
existence when thrd_create returns before pthread_main_func is
|
|
entered. So, allocate it in the heap. */
|
|
struct pthread_main_arg_t *pthread_main_arg =
|
|
(struct pthread_main_arg_t *) malloc (sizeof (struct pthread_main_arg_t));
|
|
if (pthread_main_arg == NULL)
|
|
return thrd_nomem;
|
|
pthread_main_arg->mainfunc = mainfunc;
|
|
pthread_main_arg->arg = arg;
|
|
|
|
{
|
|
int err = glwthread_thread_create (threadp, 0,
|
|
pthread_main_func, pthread_main_arg);
|
|
if (err != 0)
|
|
free (pthread_main_arg);
|
|
return (err == 0 ? thrd_success :
|
|
err == ENOMEM /* || err == EAGAIN */ ? thrd_nomem :
|
|
thrd_error);
|
|
}
|
|
}
|
|
|
|
thrd_t
|
|
thrd_current (void)
|
|
{
|
|
return glwthread_thread_self ();
|
|
}
|
|
|
|
int
|
|
thrd_equal (thrd_t thread1, thrd_t thread2)
|
|
{
|
|
return thread1 == thread2;
|
|
}
|
|
|
|
void
|
|
thrd_yield (void)
|
|
{
|
|
Sleep (0);
|
|
}
|
|
|
|
int
|
|
thrd_detach (thrd_t thread)
|
|
{
|
|
int err = glwthread_thread_detach (thread);
|
|
return (err == 0 ? thrd_success : thrd_error);
|
|
}
|
|
|
|
int
|
|
thrd_join (thrd_t thread, int *exitcodep)
|
|
{
|
|
void *exitptr;
|
|
int err = glwthread_thread_join (thread, &exitptr);
|
|
if (err == 0)
|
|
{
|
|
if (exitcodep != NULL)
|
|
*exitcodep = (int) (intptr_t) exitptr;
|
|
return thrd_success;
|
|
}
|
|
else
|
|
return thrd_error;
|
|
}
|
|
|
|
_Noreturn void
|
|
thrd_exit (int exitcode)
|
|
{
|
|
glwthread_thread_exit ((void *) (intptr_t) exitcode);
|
|
}
|
|
|
|
# else
|
|
/* Use POSIX threads. */
|
|
|
|
int
|
|
thrd_create (thrd_t *threadp, thrd_start_t mainfunc, void *arg)
|
|
{
|
|
/* Combine mainfunc and arg in a single object.
|
|
A stack-allocated object does not work, because it would be out of
|
|
existence when thrd_create returns before pthread_main_func is
|
|
entered. So, allocate it in the heap. */
|
|
struct pthread_main_arg_t *pthread_main_arg =
|
|
(struct pthread_main_arg_t *) malloc (sizeof (struct pthread_main_arg_t));
|
|
if (pthread_main_arg == NULL)
|
|
return thrd_nomem;
|
|
pthread_main_arg->mainfunc = mainfunc;
|
|
pthread_main_arg->arg = arg;
|
|
|
|
{
|
|
int err = pthread_create (threadp, NULL,
|
|
pthread_main_func, pthread_main_arg);
|
|
if (err != 0)
|
|
free (pthread_main_arg);
|
|
return (err == 0 ? thrd_success :
|
|
err == ENOMEM /* || err == EAGAIN */ ? thrd_nomem :
|
|
thrd_error);
|
|
}
|
|
}
|
|
|
|
thrd_t
|
|
thrd_current (void)
|
|
{
|
|
return pthread_self ();
|
|
}
|
|
|
|
int
|
|
thrd_equal (thrd_t thread1, thrd_t thread2)
|
|
{
|
|
return pthread_equal (thread1, thread2);
|
|
}
|
|
|
|
void
|
|
thrd_yield (void)
|
|
{
|
|
sched_yield ();
|
|
}
|
|
|
|
int
|
|
thrd_detach (thrd_t thread)
|
|
{
|
|
int err = pthread_detach (thread);
|
|
return (err == 0 ? thrd_success : thrd_error);
|
|
}
|
|
|
|
int
|
|
thrd_join (thrd_t thread, int *exitcodep)
|
|
{
|
|
void *exitptr;
|
|
int err = pthread_join (thread, &exitptr);
|
|
if (err == 0)
|
|
{
|
|
if (exitcodep != NULL)
|
|
*exitcodep = (int) (intptr_t) exitptr;
|
|
return thrd_success;
|
|
}
|
|
else
|
|
return thrd_error;
|
|
}
|
|
|
|
_Noreturn void
|
|
thrd_exit (int exitcode)
|
|
{
|
|
pthread_exit ((void *) (intptr_t) exitcode);
|
|
}
|
|
|
|
# endif
|
|
|
|
int
|
|
thrd_sleep (const struct timespec *duration, struct timespec *remaining)
|
|
{
|
|
int ret = nanosleep (duration, remaining);
|
|
return (ret == 0 ? 0 : errno == EINTR ? -1 : -2);
|
|
}
|
|
|
|
#endif
|