如何在C中创建非单次定时器?

时间:2014-09-09 19:41:32

标签: c linux timer

我需要在嵌入式Linux的C代码中使用非单次定时器(例如来自Qt库的QTimer)(无单次定时器我的意思是一次无限期地触发x秒直到“停止定时器”被调用,而不是只触发一次或一次阻止代码计数的那个)。

我可以使用的一些库使用信号处理程序实现这样的计时器,但我想避免使用这样的系统(I learned that is not the best solutio n)。我知道我可以通过重新启动计时器(再次调用它)来模拟我想要的单次计时器,这是一个可接受的解决方案(实际上我谈到的那些工作方式),但我不知道如何在没有阻止运行代码的情况下实现它,直到触发定时器。

还有一件事:我需要能够实现更多,然后只有其中一个(这里是信号处理程序停止成为可行的解决方案AFAIK)。

那我怎么能做这样的解决方案呢? Qt的QTimer提供的关闭越多越好!

2 个答案:

答案 0 :(得分:3)

如果你确实需要不同间隔/次数的未指定数量的触发器,那么专用的计时器线程(在另一个答案中由nneonneo描述)在我的经验中具有最少的陷阱。

定时器是一种有限的资源(可用的定时器数量是可配置的,因系统而异,所以你不能像那样制作任何广泛的声明“我确信这对我的目的来说足够了” )。

除非使用SA_RESTART标志,否则信号中断阻塞系统调用;即便如此,也有一些例外(详见信息处理程序},系统调用中断和库函数章节)。


专用计时器线程围绕两个组件构建:

  1. 包含所有计时器事件的队列,列表,树或堆

    典型的实现只需知道 next 事件何时发生,因此man 7 signalmin-heap的效果非常好。我发现min-heap实现起来简单而且健壮,而且插入和删除的时间复杂度足够高( O(log N)时间复杂度);使用绝对时间(在Linux中使用CLOCK_MONOTONIC)将事件作为键。

    请注意,如果您将计时器事件用作超时,则还需要确保取消事件是有效的。在正常操作中,超时很少见,因此像Web服务器这样的东西可能会取消它设置的所有超时,而其中没有任何超时实际触发。

  2. 等待下一个事件或另一个插入新计时器事件的线程的线程

    就个人而言,我使用一个数组来保存由pthread_mutex_t保护的事件的最小堆,其中pthread_cond_t用于在添加新事件后发出信号的其他线程。然后,使用priority queue在指定的时间内等待/休眠,或直到线程通知新事件(以较早者为准),这是一件简单的事情。

    当下一个事件发生时 - 请注意,由于计划,您可能会发现多个单独的事件发生,因此您可能根本不想睡觉(但您仍可能检查是否添加了新事件) - ,你执行该事件。如果事件是周期性的,那么您也将它重新插入堆/队列中,并在下次准备好。

  3. 选择如何执行事件非常重要,而且确实是唯一真正棘手的事情。您可以使用标志 - 在实践中从零切换到非零是安全的,即使更改不是原子的,只要您不依赖于任何特定的非零值 - ;您可以使条件变量发出信号或广播;你可以发布一个信号量;你可以在一个特定的线程中引发一个特定的信号(如果安装了没有SA_RESTART标志的处理程序,即使是一个空信号处理程序也会导致阻塞I / O调用中断;我已将它用作I / O超时非常成功);如果使用GCC(或Intel CC,Pathscale或Portland Group C编译器),您甚至可以使用pthread_cond_timedwait()__atomic以原子方式修改值;等等。

    如果您需要调用特定函数,我建议使用单独的线程(或者,如果应用程序/程序/游戏中的大多数工作是在这些计时器事件中完成的,则使用线程池)来执行事件。这使得计时器线程简单而精确,同时保持所有资源的使用易于控制。工作线程或线程池应该只有一个由互斥锁和条件变量保护的FIFO事件队列,以便定时器线程可以将每个事件添加到队列,然后在条件变量上发出信号以通知(下一个)工作线程这项工作是可行的。

    事实上,在我使用其他事件动作模型的几个实例中,我现在相信功能工作者模型会更容易。特别是如果你使worker函数接受一个由调用者定义的指针(到一个结构),以便它们都具有相同的签名,这个接口变得非常简单,但非常强大和通用。

    定时器线程加工作线程方法有一个缺点,那就是(最小)增加的延迟。工作线程不会在指定时间 获得工作,但不久之后。但是,如果您有工作线程获取当前时间,则与(未调整的)目标时间进行比较,并将其用作统计信息以在目标时间之前相应地触发事件,您通常可以处理此问题。 (我还没有验证过,但我确实相信Qt和GTK +工具包都会以类似的方式不断估计这种延迟。)

    有问题吗?

答案 1 :(得分:1)

您有几个选项,其中任何一个都不需要标准C和POSIX库之外的任何库。

  • POSIX计时器API,例如timer_create和朋友们。它们具有基于sigev的灵活通知方案,允许您指定通知方式(向特定线程发送信号,创建新线程或任意信号)。通过指定信号进入特定线程,您可以将该线程设置为准备好异步信号,并使用sig_atomic_t表示线程要完成的工作。最有趣的通知选项是使用全新线程的创建,但请注意,如果定时器频繁触发,这会变得很昂贵。
  • Linux timerfd API,例如timerfd_create。这些创建计时器,您可以使用pollepoll进行轮询,使您能够将计时器添加到低级事件循环,并在完美的线程安全和信号安全的情况下对它们进行操作方式。
  • alarm。这使用了SIGALRM异步信号,因此您再次使用sig_atomic_t和信号处理线程来处理计时器。
  • 专用计时器线程上的
  • selectpollnanosleep:这就是QTimer通常所做的事情。您只需创建一个专用的计时器线程,让线程重复睡眠。为了使计时器按计划进行,您可以根据每个处理周期的长度调整休眠时间。

最后一个选项是最便携的,但也基本上是最多的工作(因为你自己实现了计时器)。结果是你可以完全自定义“计时器”,因为你是在睡眠基元上实现它。