1
votes

I have a timer that runs at regular intervals. I create the timer using timer_create() using the SIGEV_THREAD option. This will fire a callback on a thread when the timer expires, rather than send a SIGALRM signal to the process. The problem is, every time my timer expires, a new thread is spawned. This means the program spawns potentially hundreds of threads, depending on the frequency of the timer. What would be better is to have one thread that handles the callbacks. I can do this when using timer_create() with signals (by using sigaction), but not threads only. Is there any way to not use signals, but still have the timer notify the process in a single existing thread? Or should I even worry about this from a performance perspective (threads vs signals)?

EDIT:

My solution was to use SIGEV_SIGNAL and pthread_sigmask(). So, I continue to rely on signals to know when my timer expires, but I can be 100% sure only a single thread (created by me) is being used to capture the signals and execute the appropriate action.

1
Presumably the newly spawned thread will terminate once the callback has completed, but it still sounds like a very expensive option if the timer fires frequently. - 500 - Internal Server Error
@500-InternalServerError That's exactly what I thought. Creating and destroying a thread for each timer expiration seems like a lot of inefficient work. But as far as I can tell, that's the way the API works. There's no option from what I can see to keep the timer firing callbacks on a single thread, other than to use signals. - Synthetix
@Synthetix +1 because interesting question (from a quick scan of the relevant Linux code it doesn't seem possible out of the box, but maybe someone will have a clever solution) - but why the aversion to signals? - Daniel Kleinstein
Consider redesigning your program to use timerfd_create instead. - o11c
@Shawn I ended up doing the latter, using SIGEV_SIGNAL and pthread_sigmask(). - Synthetix

1 Answers

2
votes

tl;dr: The basic premise that SIGEV_THREAD doesn't work based on signals is false - signals are the underlying mechanism through which new threads are spawned. glibc has no support for reutilizing the same thread for multiple callbacks.


timer_create doesn't behave exactly the way you think - its second parameter, struct sigevent *restrict sevp contains the field sigevent_notify which has following documentation:

SIGEV_THREAD

Notify the process by invoking sigev_notify_function "as if" it were the start function of a new thread. (Among the implementation possibilities here are that each timer notification could result in the creation of a new thread, or that a single thread is created to receive all notifications.) The function is invoked with sigev_value as its sole argument. If sigev_notify_attributes is not NULL, it should point to a pthread_attr_t structure that defines attributes for the new thread (see pthread_attr_init(3)).

And indeed, if we look at glibc's implementation:

else
      {
    /* Create the helper thread.  */
    pthread_once (&__helper_once, __start_helper_thread);

    ...

    struct sigevent sev =
      { .sigev_value.sival_ptr = newp,
        .sigev_signo = SIGTIMER,
        .sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID,
        ._sigev_un = { ._pad = { [0] = __helper_tid } } };

    /* Create the timer.  */
    INTERNAL_SYSCALL_DECL (err);
    int res;
    res = INTERNAL_SYSCALL (timer_create, err, 3,
                syscall_clockid, &sev, &newp->ktimerid);

And we can see __start_helper_thread's implementation:

void
attribute_hidden
__start_helper_thread (void)
{
  ...
  int res = pthread_create (&th, &attr, timer_helper_thread, NULL);

And follow along to timer_helper_thread's implementation:

static void *
timer_helper_thread (void *arg)
{
  ...

  /* Endless loop of waiting for signals.  The loop is only ended when
     the thread is canceled.  */
  while (1)
    {
      ...
      int result = SYSCALL_CANCEL (rt_sigtimedwait, &ss, &si, NULL, _NSIG / 8);

      if (result > 0)
    {
      if (si.si_code == SI_TIMER)
        {
          struct timer *tk = (struct timer *) si.si_ptr;
          ...
            (void) pthread_create (&th, &tk->attr,
                        timer_sigev_thread, td);

So - at least at the glibc level - when using SIGEV_THREAD you are necessarily using signals to signal a thread to create the function anyways - and it seems like your primary motivation to begin with was avoiding the use of alarm signals.

At the Linux source code level, timers seems to work on signals alone - the posix_timer_event in kernel/time/posix_timers.c function (called by alarm_handle_timer in kernel/time/alarmtimer.c) goes straight to code in signal.c that necessarily sends a signal. So it doesn't seem possible to avoid signals when working with timer_create, and this statement from your question - "This will fire a callback on a thread when the timer expires, rather than send a SIGALRM signal to the process." - is false (though it's true that the signal doesn't have to be SIGALRM).

In other words - there seem to be no performance benefits to be gained from SIGEV_THREAD as opposed to signals. Signals will still be used to trigger the creation of threads, and you're adding the additional overhead of creating new threads.