使用 SIGEV_THREAD 时如何为所有 timer_create 回调重用一个线程?

How can I reuse a single thread for all timer_create callbacks when using SIGEV_THREAD?

我有一个定时运行的计时器。我使用 timer_create() 使用 SIGEV_THREAD 选项创建计时器。这将在计时器到期时在线程上触发回调,而不是向进程发送 SIGALRM 信号。问题是,每次我的计时器到期时,都会产生一个新线程。这意味着程序可能会产生数百个线程,具体取决于计时器的频率。 更好的办法是让 一个线程 来处理回调。我可以在将 timer_create() 与信号一起使用时执行此操作(通过使用 sigaction),但不仅仅是线程。 有没有办法不使用信号,但仍然让计时器在单个现有线程中通知进程? 或者我是否应该从性能角度(线程与信号)担心这一点?

编辑:

我的解决方案是使用 SIGEV_SIGNALpthread_sigmask()。因此,我继续依靠信号来了解我的计时器何时到期,但我可以 100% 确定只有一个线程(由我创建)用于捕获信号并执行适当的操作。

tl;dr:SIGEV_THREAD 不能基于信号工作的基本前提是错误的 - 信号是生成新线程的基础机制。 glibc 不支持为多个回调重新利用同一个线程。


timer_create 的行为并不完全符合您的想法 - 它的第二个参数 struct sigevent *restrict sevp 包含字段 sigevent_notify,它具有以下 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)).

确实,如果我们看一下 glibc 的 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);

我们可以看到 __start_helper_threadimplementation:

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

然后跟随 timer_helper_threadimplementation:

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);

所以 - 至少在 glibc 级别 - 当使用 SIGEV_THREAD 时,你 必须 使用信号来通知线程无论如何都要创建函数 - 看起来您开始的主要动机是避免使用警报信号。

在 Linux 源代码级别,定时器似乎只对信号起作用 - kernel/time/posix_timers.c 函数中的 posix_timer_event (由 alarm_handle_timer 在 [=23= 中调用) ]) 直接转到 signal.c 中必须发送信号的代码。因此,在使用 timer_create 时似乎无法避免信号,而您的问题中的这个陈述 - “这将在计时器到期时在线程上触发回调,而不是向进程发送 SIGALRM 信号。 “ - 是假的(尽管信号不一定是 SIGALRM 是真的)。​​

换句话说 - 与信号相反,SIGEV_THREAD 似乎没有性能优势。信号仍将用于触发线程的创建,并且您正在添加创建新线程的额外开销。