diff --git a/nptl/descr.h b/nptl/descr.h
index 746a4b9e4a..0874c2d64c 100644
--- a/nptl/descr.h
+++ b/nptl/descr.h
@@ -408,6 +408,9 @@ struct pthread
   /* rseq area registered with the kernel.  */
   struct rseq rseq_area;
 
+  /* POSIX per-process timer.  */
+  int timerid;
+
   /* This member must be last.  */
   char end_padding[];
 
diff --git a/rt/Makefile b/rt/Makefile
index a97333dc02..0c93a7f79d 100644
--- a/rt/Makefile
+++ b/rt/Makefile
@@ -99,6 +99,7 @@ include ../Rules
 CFLAGS-aio_suspend.c += -fexceptions
 CFLAGS-mq_timedreceive.c += -fexceptions -fasynchronous-unwind-tables
 CFLAGS-mq_timedsend.c += -fexceptions -fasynchronous-unwind-tables
+CFLAGS-timer_create.c += -fexceptions -fasynchronous-unwind-tables
 
 LDFLAGS-rt.so = -Wl,--enable-new-dtags,-z,nodelete
 
diff --git a/rt/tst-timer-sigmask.c b/rt/tst-timer-sigmask.c
index a6c6d53778..3a2504ade8 100644
--- a/rt/tst-timer-sigmask.c
+++ b/rt/tst-timer-sigmask.c
@@ -29,29 +29,43 @@
 
 static pthread_barrier_t barrier;
 
+static void
+__attribute_used__
+cl (void *arg)
+{
+  xpthread_barrier_wait (&barrier);
+}
+
 static void
 thread_handler (union sigval sv)
 {
-  sigset_t ss;
-  sigprocmask (SIG_BLOCK, NULL, &ss);
-  if (test_verbose > 0)
-    printf ("%s: blocked signal mask = { ", __func__);
-  for (int sig = 1; sig < NSIG; sig++)
-    {
-      /* POSIX timers threads created to handle SIGEV_THREAD block all
-	 signals except SIGKILL, SIGSTOP and glibc internals ones.  */
-      if (sigismember (&ss, sig))
-	{
-	  TEST_VERIFY (sig != SIGKILL && sig != SIGSTOP);
-	  TEST_VERIFY (!is_internal_signal (sig));
-	}
-      if (test_verbose && sigismember (&ss, sig))
-	printf ("%d, ", sig);
-    }
-  if (test_verbose > 0)
-    printf ("}\n");
+  /* POSIX timers threads created to handle SIGEV_THREAD block all
+     signals except SIGKILL, SIGSTOP, and SIGSETXID.  */
+  sigset_t expected_set = {
+    .__val = {[0 ...  __NSIG_WORDS-1 ] = -1 }
+  };
+  __sigdelset (&expected_set, SIGSETXID);
+  __sigdelset (&expected_set, SIGKILL);
+  __sigdelset (&expected_set, SIGSTOP);
 
-  xpthread_barrier_wait (&barrier);
+  sigset_t ss;
+  sigprocmask (SIG_SETMASK, NULL, &ss);
+
+  TEST_COMPARE_BLOB (&ss.__val, __NSIG_BYTES, &expected_set, __NSIG_BYTES);
+
+  /* Unblock some signals, the signal mask should be reset by the timer_create
+     helper thread.  */
+  {
+    sigset_t toset;
+    sigemptyset (&toset);
+    sigaddset (&toset, SIGHUP);
+    sigaddset (&toset, SIGILL);
+    sigprocmask (SIG_UNBLOCK, &toset, NULL);
+  }
+
+  pthread_cleanup_push (cl, NULL);
+  pthread_exit (NULL);
+  pthread_cleanup_pop (0);
 }
 
 static int
@@ -66,11 +80,12 @@ do_test (void)
 
   xpthread_barrier_init (&barrier, NULL, 2);
 
-  struct itimerspec trigger = { 0 };
-  trigger.it_value.tv_nsec = 1000000;
+  struct itimerspec trigger = { { 0, 1000000 }, { 0, 1000000 } };
   TEST_COMPARE (timer_settime (timerid, 0, &trigger, NULL), 0);
 
-  xpthread_barrier_wait (&barrier);
+  enum { TIMERS_TO_CHECK = 2 };
+  for (int i = 0; i < TIMERS_TO_CHECK; i++)
+    xpthread_barrier_wait (&barrier);
 
   return 0;
 }
diff --git a/sysdeps/nptl/fork.h b/sysdeps/nptl/fork.h
index ee7967416c..40ed5cf7e7 100644
--- a/sysdeps/nptl/fork.h
+++ b/sysdeps/nptl/fork.h
@@ -20,7 +20,6 @@
 #define _FORK_H
 
 #include <assert.h>
-#include <kernel-posix-timers.h>
 #include <ldsodefs.h>
 #include <list.h>
 #include <mqueue.h>
@@ -45,7 +44,6 @@ fork_system_setup_after_fork (void)
   __default_pthread_attr_lock = LLL_LOCK_INITIALIZER;
 
   call_function_static_weak (__mq_notify_fork_subprocess);
-  call_function_static_weak (__timer_fork_subprocess);
 }
 
 /* In case of a fork() call the memory allocation in the child will be
diff --git a/sysdeps/pthread/tst-cancel28.c b/sysdeps/pthread/tst-cancel28.c
index ae55f38210..2eb78acede 100644
--- a/sysdeps/pthread/tst-cancel28.c
+++ b/sysdeps/pthread/tst-cancel28.c
@@ -28,48 +28,64 @@
 #include <support/xthread.h>
 
 static pthread_barrier_t barrier;
-static pthread_t timer_thread;
+
+#define VALUE1_INITIAL 0
+static __thread unsigned int value1 = VALUE1_INITIAL;
+#define VALUE2_INITIAL 0xdeadbeef
+static __thread unsigned int value2 = VALUE2_INITIAL;
+
+static int cl_called = 0;
 
 static void
 cl (void *arg)
 {
-  xpthread_barrier_wait (&barrier);
+  cl_called++;
+  xpthread_barrier_wait (&barrier); /* 1 */
 }
 
 static void
 thread_handler (union sigval sv)
 {
-  timer_thread = pthread_self ();
+  /* Check if thread state is correctly reset after a pthread_exit.  */
+  TEST_COMPARE (value1, VALUE1_INITIAL);
+  TEST_COMPARE (value2, VALUE2_INITIAL);
+
+  value1 = VALUE2_INITIAL;
+  value2 = VALUE1_INITIAL;
 
-  xpthread_barrier_wait (&barrier);
+  xpthread_barrier_wait (&barrier);  /* 0 */
 
   pthread_cleanup_push (cl, NULL);
-  while (1)
-    clock_nanosleep (CLOCK_REALTIME, 0, &(struct timespec) { 1, 0 }, NULL);
+  pthread_exit (NULL);
   pthread_cleanup_pop (0);
 }
 
 static int
 do_test (void)
 {
-  struct sigevent sev = { 0 };
-  sev.sigev_notify = SIGEV_THREAD;
-  sev.sigev_notify_function = &thread_handler;
+  struct sigevent sev = {
+    .sigev_notify = SIGEV_THREAD,
+    .sigev_notify_function = &thread_handler,
+  };
 
   timer_t timerid;
   TEST_COMPARE (timer_create (CLOCK_REALTIME, &sev, &timerid), 0);
 
   xpthread_barrier_init (&barrier, NULL, 2);
 
-  struct itimerspec trigger = { 0 };
-  trigger.it_value.tv_nsec = 1000000;
+  struct itimerspec trigger = { { 0, 1000000 }, { 0, 1000000 } };
   TEST_COMPARE (timer_settime (timerid, 0, &trigger, NULL), 0);
 
-  xpthread_barrier_wait (&barrier);
+  enum { TIMERS_TO_CHECK = 4 };
+  for (int i = 0; i < TIMERS_TO_CHECK; i++)
+    {
+      /* Check the thread local values.  */
+      xpthread_barrier_wait (&barrier);  /* 0 */
 
-  xpthread_cancel (timer_thread);
+      xpthread_barrier_wait (&barrier);  /* 1 */
+    }
 
-  xpthread_barrier_wait (&barrier);
+  TEST_COMPARE (cl_called, TIMERS_TO_CHECK);
 
   return 0;
 }
diff --git a/sysdeps/unix/sysv/linux/internal-signals.h b/sysdeps/unix/sysv/linux/internal-signals.h
index 43c3c0b4a0..e83b48a58e 100644
--- a/sysdeps/unix/sysv/linux/internal-signals.h
+++ b/sysdeps/unix/sysv/linux/internal-signals.h
@@ -99,12 +99,4 @@ static const sigset_t sigtimer_set = {
   }
 };
 
-/* Unblock only SIGTIMER.  */
-static inline void
-signal_unblock_sigtimer (void)
-{
-  INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_UNBLOCK, &sigtimer_set, NULL,
-			 __NSIG_BYTES);
-}
-
 #endif
diff --git a/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h b/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h
index bea1e0e62d..eda53be167 100644
--- a/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h
+++ b/sysdeps/unix/sysv/linux/kernel-posix-cpu-timers.h
@@ -8,6 +8,8 @@
   - A clockid is invalid if bits 2, 1, and 0 are all set.
  */
 
+#include <time.h>
+
 #define CPUCLOCK_PID(clock)		((pid_t) ~((clock) >> 3))
 #define CPUCLOCK_PERTHREAD(clock) \
 	(((clock) & (clockid_t) CPUCLOCK_PERTHREAD_MASK) != 0)
diff --git a/sysdeps/unix/sysv/linux/kernel-posix-timers.h b/sysdeps/unix/sysv/linux/kernel-posix-timers.h
index 9d86bb1dab..cd6bf89acd 100644
--- a/sysdeps/unix/sysv/linux/kernel-posix-timers.h
+++ b/sysdeps/unix/sysv/linux/kernel-posix-timers.h
@@ -19,29 +19,7 @@
 #include <setjmp.h>
 #include <signal.h>
 #include <sys/types.h>
-
-
-/* Nonzero if the system calls are not available.  */
-extern int __no_posix_timers attribute_hidden;
-
-/* Callback to start helper thread.  */
-extern void __timer_start_helper_thread (void) attribute_hidden;
-
-/* Control variable for helper thread creation.  */
-extern pthread_once_t __timer_helper_once attribute_hidden;
-
-/* Called from fork so that the new subprocess re-creates the
-   notification thread if necessary.  */
-void __timer_fork_subprocess (void) attribute_hidden;
-
-/* TID of the helper thread.  */
-extern pid_t __timer_helper_tid attribute_hidden;
-
-/* List of active SIGEV_THREAD timers.  */
-extern struct timer *__timer_active_sigev_thread attribute_hidden;
-
-/* Lock for __timer_active_sigev_thread.  */
-extern pthread_mutex_t __timer_active_sigev_thread_lock attribute_hidden;
+#include <nptl/descr.h>
 
 extern __typeof (timer_create) __timer_create;
 libc_hidden_proto (__timer_create)
@@ -53,25 +31,9 @@ libc_hidden_proto (__timer_getoverrun)
 /* Type of timers in the kernel.  */
 typedef int kernel_timer_t;
 
-/* Internal representation of SIGEV_THREAD timer.  */
-struct timer
-{
-  kernel_timer_t ktimerid;
-
-  void (*thrfunc) (sigval_t);
-  sigval_t sival;
-  pthread_attr_t attr;
-
-  /* Next element in list of active SIGEV_THREAD timers.  */
-  struct timer *next;
-};
-
-
 /* For !SIGEV_THREAD, the resulting 'timer_t' is the returned kernel timer
-   identifier (kernel_timer_t), while for SIGEV_THREAD it uses the fact malloc
-   returns at least _Alignof (max_align_t) pointers plus that valid
-   kernel_timer_t are always positive to set the MSB bit of the returned
-   'timer_t' to indicate the timer handles a SIGEV_THREAD.  */
+   identifier (kernel_timer_t), while for SIGEV_THREAD it assumes the
+   pthread_t is always 8-byte aligned.  */
 
 static inline timer_t
 kernel_timer_to_timerid (kernel_timer_t ktimerid)
@@ -80,7 +42,7 @@ kernel_timer_to_timerid (kernel_timer_t ktimerid)
 }
 
 static inline timer_t
-timer_to_timerid (struct timer *ptr)
+pthread_to_timerid (pthread_t ptr)
 {
   return (timer_t) (INTPTR_MIN | (uintptr_t) ptr >> 1);
 }
@@ -91,17 +53,17 @@ timer_is_sigev_thread (timer_t timerid)
   return (intptr_t) timerid < 0;
 }
 
-static inline struct timer *
-timerid_to_timer (timer_t timerid)
+static inline struct pthread *
+timerid_to_pthread (timer_t timerid)
 {
-  return (struct timer *)((uintptr_t) timerid << 1);
+  return (struct pthread *)((uintptr_t) timerid << 1);
 }
 
 static inline kernel_timer_t
 timerid_to_kernel_timer (timer_t timerid)
 {
   if (timer_is_sigev_thread (timerid))
-    return timerid_to_timer (timerid)->ktimerid;
+    return timerid_to_pthread (timerid)->timerid;
   else
     return (kernel_timer_t) ((uintptr_t) timerid);
 }
diff --git a/sysdeps/unix/sysv/linux/timer_create.c b/sysdeps/unix/sysv/linux/timer_create.c
index b7e84e2bb1..1aa13abb21 100644
--- a/sysdeps/unix/sysv/linux/timer_create.c
+++ b/sysdeps/unix/sysv/linux/timer_create.c
@@ -15,46 +15,200 @@
    License along with the GNU C Library; see the file COPYING.LIB.  If
    not, see <https://www.gnu.org/licenses/>.  */
 
-#include <errno.h>
-#include <pthread.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <sysdep.h>
-#include <internaltypes.h>
+#include <jmpbuf-unwind.h>
+#include <kernel-posix-cpu-timers.h>
+#include <kernel-posix-timers.h>
+#include <ldsodefs.h>
+#include <libc-internal.h>
+#include <libc-lock.h>
 #include <pthreadP.h>
-#include "kernel-posix-timers.h"
-#include "kernel-posix-cpu-timers.h"
 #include <shlib-compat.h>
 
+struct timer_helper_thread_args_t
+{
+  /* The barrier is used to synchronize the arguments copy from timer_create
+     and the SIGEV_THREAD thread and to instruct the thread to exit if the
+     timer_create syscall fails.  */
+  pthread_barrier_t b;
+  struct sigevent *evp;
+};
+
+struct cleanup_args_t
+{
+  struct pthread_unwind_buf *cleanup_jmp_buf;
+  jmp_buf jb;
+};
+
+/* Reset internal thread state if the callback issues pthread_exit.  It avoids
+   recreating the thread and having possible unreported missed events due
+   thread creation failure.  */
+static void
+timer_helper_thread_cleanup (void *arg)
+{
+  struct pthread *self = THREAD_SELF;
+
+  /* Call destructors for the thread_local TLS variables.  */
+#ifndef SHARED
+  if (&__call_tls_dtors != NULL)
+#endif
+    __call_tls_dtors ();
+
+  /* Run the destructor for the thread-local data.  */
+  __nptl_deallocate_tsd ();
+
+  /* Clean up any state libc stored in thread-local variables.  */
+  __libc_thread_freeres ();
+
+  /* Reset internal TCB state.  */
+  struct cleanup_args_t *args = arg;
+  self->cleanup_jmp_buf = args->cleanup_jmp_buf;
+  self->cleanup_jmp_buf->priv.data.prev = NULL;
+  self->cleanup_jmp_buf->priv.data.cleanup = NULL;
+  self->cleanup_jmp_buf->priv.data.canceltype = 0;
+  self->cleanup = NULL;
+  self->exc = (struct _Unwind_Exception) { 0 };
+  self->cancelhandling = 0;
+  self->nextevent = NULL;
+
+  /* Re-initialize the TLS.  */
+  _dl_allocate_tls_init (TLS_TPADJ (self), true);
+
+  /* Reset to the expected initial signal mask.  */
+  internal_sigset_t ss;
+  internal_sigfillset (&ss);
+  internal_sigdelset (&ss, SIGSETXID);
+  internal_sigprocmask (SIG_SETMASK, &ss, NULL);
+
+  /* There is no need to perform any additional cleanup by the frames.  */
+  struct __jmp_buf_tag *env = args->jb;
+  __longjmp (env[0].__jmpbuf, 1);
+}
+
+static void *
+timer_helper_thread (void *arg)
+{
+  struct pthread *self = THREAD_SELF;
+
+  struct timer_helper_thread_args_t *args = arg;
+
+  void (*thrfunc) (sigval_t) = args->evp->sigev_notify_function;
+  sigval_t sival = args->evp->sigev_value;
+  __pthread_barrier_wait (&args->b);
+
+  /* timer_create syscall failed.  */
+  if (self->timerid < 0)
+    return 0;
+
+  struct cleanup_args_t clargs = {
+    .cleanup_jmp_buf = self->cleanup_jmp_buf
+  };
+
+  while (1)
+    {
+      siginfo_t si;
+      while (__sigwaitinfo (&sigtimer_set, &si) < 0) {};
+
+      if (si.si_code == SI_TIMER && !setjmp (clargs.jb))
+	{
+	  pthread_cleanup_push (timer_helper_thread_cleanup, &clargs);
+	  thrfunc (sival);
+	  pthread_cleanup_pop (0);
+	}
+
+      /* timer_delete will set the MSB and signal the thread.  */
+      if (atomic_load_relaxed (&self->timerid) < 0)
+	break;
+    }
+
+  /* Clear the MSB bit set by timer_delete.  */
+  INTERNAL_SYSCALL_CALL (timer_delete, self->timerid & INT_MAX);
+
+  return NULL;
+}
+
+static int
+timer_create_sigev_thread (clockid_t syscall_clockid, struct sigevent *evp,
+			   timer_t *timerid)
+{
+  int ret = -1;
+
+  pthread_attr_t attr;
+  if (evp->sigev_notify_attributes != NULL)
+    __pthread_attr_copy (&attr, evp->sigev_notify_attributes);
+  else
+    __pthread_attr_init (&attr);
+  __pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+
+  /* Block all signals in the helper thread but SIGSETXID.  */
+  sigset_t ss;
+  __sigfillset (&ss);
+  __sigdelset (&ss, SIGSETXID);
+  ret = __pthread_attr_setsigmask_internal (&attr, &ss);
+  if (ret != 0)
+    goto out;
+
+  struct timer_helper_thread_args_t args;
+  __pthread_barrier_init (&args.b, NULL, 2);
+  args.evp = evp;
+
+  pthread_t th;
+  ret = __pthread_create (&th, &attr, timer_helper_thread, &args);
+  if (ret != 0)
+    {
+      __set_errno (ret);
+      goto out;
+    }
+
+  struct sigevent kevp = {
+    .sigev_value.sival_ptr = NULL,
+    .sigev_signo = SIGTIMER,
+    .sigev_notify = SIGEV_THREAD_ID,
+    ._sigev_un = { ._pad = { [0] = ((struct pthread *)th)->tid } }
+  };
+
+  struct pthread *pthr = (struct pthread *)th;
+
+  kernel_timer_t ktimerid;
+  if (INLINE_SYSCALL_CALL (timer_create, syscall_clockid, &kevp, &ktimerid) < 0)
+    pthr->timerid = -1;
+  pthr->timerid = ktimerid;
+  /* Signal the thread to continue execution after it copies the arguments
+     or exit if the timer can not be created.  */
+  __pthread_barrier_wait (&args.b);
+
+  if (timerid >= 0)
+    *timerid = pthread_to_timerid (th);
+
+  ret = 0;
+out:
+  if (&attr != evp->sigev_notify_attributes)
+    __pthread_attr_destroy (&attr);
+
+  return ret;
+}
+
 int
 ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
 {
-  {
-    clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID
-				 ? PROCESS_CLOCK
-				 : clock_id == CLOCK_THREAD_CPUTIME_ID
-				 ? THREAD_CLOCK
-				 : clock_id);
-
-    /* If the user wants notification via a thread we need to handle
-       this special.  */
-    if (evp == NULL
-	|| __builtin_expect (evp->sigev_notify != SIGEV_THREAD, 1))
-      {
-	struct sigevent local_evp;
+  clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID
+			       ? PROCESS_CLOCK
+			       : clock_id == CLOCK_THREAD_CPUTIME_ID
+			       ? THREAD_CLOCK
+			       : clock_id);
 
+  switch (evp != NULL ? evp->sigev_notify : SIGEV_SIGNAL)
+    {
+    case SIGEV_NONE:
+    case SIGEV_SIGNAL:
+    case SIGEV_THREAD_ID:
+      {
+	struct sigevent kevp;
 	if (evp == NULL)
 	  {
-	    /* The kernel has to pass up the timer ID which is a
-	       userlevel object.  Therefore we cannot leave it up to
-	       the kernel to determine it.  */
-	    local_evp.sigev_notify = SIGEV_SIGNAL;
-	    local_evp.sigev_signo = SIGALRM;
-	    local_evp.sigev_value.sival_ptr = NULL;
-
-	    evp = &local_evp;
+	    kevp.sigev_notify = SIGEV_SIGNAL;
+	    kevp.sigev_signo = SIGALRM;
+	    kevp.sigev_value.sival_ptr = NULL;
+	    evp = &kevp;
 	  }
 
 	kernel_timer_t ktimerid;
@@ -64,75 +218,15 @@ ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
 
 	*timerid = kernel_timer_to_timerid (ktimerid);
       }
-    else
-      {
-	/* Create the helper thread.  */
-	__pthread_once (&__timer_helper_once, __timer_start_helper_thread);
-	if (__timer_helper_tid == 0)
-	  {
-	    /* No resources to start the helper thread.  */
-	    __set_errno (EAGAIN);
-	    return -1;
-	  }
-
-	struct timer *newp = malloc (sizeof (struct timer));
-	if (newp == NULL)
-	  return -1;
-
-	/* Copy the thread parameters the user provided.  */
-	newp->sival = evp->sigev_value;
-	newp->thrfunc = evp->sigev_notify_function;
-
-	/* We cannot simply copy the thread attributes since the
-	   implementation might keep internal information for
-	   each instance.  */
-	__pthread_attr_init (&newp->attr);
-	if (evp->sigev_notify_attributes != NULL)
-	  {
-	    struct pthread_attr *nattr;
-	    struct pthread_attr *oattr;
-
-	    nattr = (struct pthread_attr *) &newp->attr;
-	    oattr = (struct pthread_attr *) evp->sigev_notify_attributes;
-
-	    nattr->schedparam = oattr->schedparam;
-	    nattr->schedpolicy = oattr->schedpolicy;
-	    nattr->flags = oattr->flags;
-	    nattr->guardsize = oattr->guardsize;
-	    nattr->stackaddr = oattr->stackaddr;
-	    nattr->stacksize = oattr->stacksize;
-	  }
-
-	/* In any case set the detach flag.  */
-	__pthread_attr_setdetachstate (&newp->attr, PTHREAD_CREATE_DETACHED);
-
-	/* Create the event structure for the kernel timer.  */
-	struct sigevent sev =
-	  { .sigev_value.sival_ptr = newp,
-	    .sigev_signo = SIGTIMER,
-	    .sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID,
-	    ._sigev_un = { ._pad = { [0] = __timer_helper_tid } } };
-
-	/* Create the timer.  */
-	int res;
-	res = INTERNAL_SYSCALL_CALL (timer_create, syscall_clockid, &sev,
-				     &newp->ktimerid);
-	if (INTERNAL_SYSCALL_ERROR_P (res))
-	  {
-	    free (newp);
-	    __set_errno (INTERNAL_SYSCALL_ERRNO (res));
-	    return -1;
-	  }
-
-	/* Add to the queue of active timers with thread delivery.  */
-	__pthread_mutex_lock (&__timer_active_sigev_thread_lock);
-	newp->next = __timer_active_sigev_thread;
-	__timer_active_sigev_thread = newp;
-	__pthread_mutex_unlock (&__timer_active_sigev_thread_lock);
-
-	*timerid = timer_to_timerid (newp);
-      }
-  }
+      break;
+    case SIGEV_THREAD:
+      if (timer_create_sigev_thread (syscall_clockid, evp, timerid) < 0)
+	return -1;
+      break;
+    default:
+      __set_errno (EINVAL);
+      return -1;
+    }
 
   return 0;
 }
diff --git a/sysdeps/unix/sysv/linux/timer_delete.c b/sysdeps/unix/sysv/linux/timer_delete.c
index a611d5013a..56f8c3ed1e 100644
--- a/sysdeps/unix/sysv/linux/timer_delete.c
+++ b/sysdeps/unix/sysv/linux/timer_delete.c
@@ -26,42 +26,17 @@
 int
 ___timer_delete (timer_t timerid)
 {
-  kernel_timer_t ktimerid = timerid_to_kernel_timer (timerid);
-  int res = INLINE_SYSCALL_CALL (timer_delete, ktimerid);
-
-  if (res == 0)
+  if (timer_is_sigev_thread (timerid))
     {
-      if (timer_is_sigev_thread (timerid))
-	{
-	  struct timer *kt = timerid_to_timer (timerid);
-
-	  /* Remove the timer from the list.  */
-	  __pthread_mutex_lock (&__timer_active_sigev_thread_lock);
-	  if (__timer_active_sigev_thread == kt)
-	    __timer_active_sigev_thread = kt->next;
-	  else
-	    {
-	      struct timer *prevp = __timer_active_sigev_thread;
-	      while (prevp->next != NULL)
-		if (prevp->next == kt)
-		  {
-		    prevp->next = kt->next;
-		    break;
-		  }
-		else
-		  prevp = prevp->next;
-	    }
-	  __pthread_mutex_unlock (&__timer_active_sigev_thread_lock);
-
-	  free (kt);
-	}
+      struct pthread *th = timerid_to_pthread (timerid);
 
+      /* The helper thread itself will be responsible to call the
+	 timer_delete syscall.  */
+      atomic_fetch_or_relaxed (&th->timerid, INT_MIN);
+      __pthread_kill_internal ((pthread_t) th, SIGTIMER);
       return 0;
     }
-
-  /* The kernel timer is not known or something else bad happened.
-     Return the error.  */
-  return -1;
+  return INLINE_SYSCALL_CALL (timer_delete, timerid);
 }
 versioned_symbol (libc, ___timer_delete, timer_delete, GLIBC_2_34);
 libc_hidden_ver (___timer_delete, __timer_delete)
diff --git a/sysdeps/unix/sysv/linux/timer_routines.c b/sysdeps/unix/sysv/linux/timer_routines.c
index 2ad1e7baf2..6e25b021ab 100644
--- a/sysdeps/unix/sysv/linux/timer_routines.c
+++ b/sysdeps/unix/sysv/linux/timer_routines.c
@@ -1,154 +1 @@
-/* Copyright (C) 2003-2023 Free Software Foundation, Inc.
-   This file is part of the GNU C Library.
-
-   The GNU C Library 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.
-
-   The GNU C Library 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 the GNU C Library; see the file COPYING.LIB.  If
-   not, see <https://www.gnu.org/licenses/>.  */
-
-#include <errno.h>
-#include <setjmp.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <sysdep-cancel.h>
-#include <pthreadP.h>
-#include "kernel-posix-timers.h"
-
-
-/* List of active SIGEV_THREAD timers.  */
-struct timer *__timer_active_sigev_thread;
-
-/* Lock for _timer_active_sigev_thread.  */
-pthread_mutex_t __timer_active_sigev_thread_lock = PTHREAD_MUTEX_INITIALIZER;
-
-struct thread_start_data
-{
-  void (*thrfunc) (sigval_t);
-  sigval_t sival;
-};
-
-
-/* Helper thread to call the user-provided function.  */
-static void *
-timer_sigev_thread (void *arg)
-{
-  signal_unblock_sigtimer ();
-
-  struct thread_start_data *td = (struct thread_start_data *) arg;
-  void (*thrfunc) (sigval_t) = td->thrfunc;
-  sigval_t sival = td->sival;
-
-  /* The TD object was allocated in timer_helper_thread.  */
-  free (td);
-
-  /* Call the user-provided function.  */
-  thrfunc (sival);
-
-  return NULL;
-}
-
-
-/* Helper function to support starting threads for SIGEV_THREAD.  */
-static _Noreturn void *
-timer_helper_thread (void *arg)
-{
-  /* Endless loop of waiting for signals.  The loop is only ended when
-     the thread is canceled.  */
-  while (1)
-    {
-      siginfo_t si;
-
-      while (__sigwaitinfo (&sigtimer_set, &si) < 0);
-      if (si.si_code == SI_TIMER)
-	{
-	  struct timer *tk = (struct timer *) si.si_ptr;
-
-	  /* Check the timer is still used and will not go away
-	     while we are reading the values here.  */
-	  __pthread_mutex_lock (&__timer_active_sigev_thread_lock);
-
-	  struct timer *runp = __timer_active_sigev_thread;
-	  while (runp != NULL)
-	    if (runp == tk)
-	      break;
-	  else
-	    runp = runp->next;
-
-	  if (runp != NULL)
-	    {
-	      struct thread_start_data *td = malloc (sizeof (*td));
-
-	      /* There is not much we can do if the allocation fails.  */
-	      if (td != NULL)
-		{
-		  /* This is the signal we are waiting for.  */
-		  td->thrfunc = tk->thrfunc;
-		  td->sival = tk->sival;
-
-		  pthread_t th;
-		  __pthread_create (&th, &tk->attr, timer_sigev_thread, td);
-		}
-	    }
-
-	  __pthread_mutex_unlock (&__timer_active_sigev_thread_lock);
-	}
-    }
-}
-
-
-/* Control variable for helper thread creation.  */
-pthread_once_t __timer_helper_once = PTHREAD_ONCE_INIT;
-
-
-/* TID of the helper thread.  */
-pid_t __timer_helper_tid;
-
-
-/* Reset variables so that after a fork a new helper thread gets started.  */
-void
-__timer_fork_subprocess (void)
-{
-  __timer_helper_once = PTHREAD_ONCE_INIT;
-  __timer_helper_tid = 0;
-}
-
-
-void
-__timer_start_helper_thread (void)
-{
-  /* The helper thread needs only very little resources
-     and should go away automatically when canceled.  */
-  pthread_attr_t attr;
-  __pthread_attr_init (&attr);
-  __pthread_attr_setstacksize (&attr, __pthread_get_minstack (&attr));
-
-  /* Block all signals in the helper thread but SIGSETXID.  */
-  sigset_t ss;
-  __sigfillset (&ss);
-  __sigdelset (&ss, SIGSETXID);
-  int res = __pthread_attr_setsigmask_internal (&attr, &ss);
-  if (res != 0)
-    {
-      __pthread_attr_destroy (&attr);
-      return;
-    }
-
-  /* Create the helper thread for this timer.  */
-  pthread_t th;
-  res = __pthread_create (&th, &attr, timer_helper_thread, NULL);
-  if (res == 0)
-    /* We managed to start the helper thread.  */
-    __timer_helper_tid = ((struct pthread *) th)->tid;
-
-  /* No need for the attribute anymore.  */
-  __pthread_attr_destroy (&attr);
-}
+/* Empty.  */
