【问题标题】:How to create a non single-shot timer in C?如何在 C 中创建非单次计时器?
【发布时间】:2014-09-09 19:41:31
【问题描述】:

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

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

还有一件事:我需要能够实现的不仅仅是其中之一(这就是信号处理程序不再是可行的解决方案 AFAIK 的地方)。

那么我怎么能做这样的解决方案呢? Qt 的 QTimer 提供的功能越接近越好!

【问题讨论】:

    标签: c linux timer


    【解决方案1】:

    如果您确实需要以不同的时间间隔/时间进行未指定数量的触发器,那么根据我的经验,专用计时器线程(如 nneonneo 在另一个答案中所述)的陷阱数量最少。

    计时器是一种有限的资源(可用的计时器数量是可配置的,并且因系统而异,因此您不能做出任何笼统的陈述,例如 “我确定我的目的已经足够了” )。

    除非使用SA_RESTART标志,否则信号中断阻塞系统调用;即便如此,也有一些例外(请参阅man 7 signal信号处理程序中断系统调用和库函数一章了解详细信息)。


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

    1. 保存所有计时器事件的队列、列表、树或堆

      典型的实现只需要知道 next 事件何时发生,因此min-heappriority queue 工作得很好。我发现最小堆实现起来简单且健壮,并且足够高效(插入和删除的时间复杂度O(log N));使用事件的绝对时间(在 Linux 中使用 CLOCK_MONOTONIC)作为键。

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

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

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

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

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

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

    确实,在我使用其他事件操作模型的几个例子中,我现在相信函数工作者模型会更容易。尤其是如果你让工作函数接受一个由调用者定义的指针(指向一个结构),这样它们都具有相同的签名,这个接口就变得非常容易实现,而且非常强大和通用。

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

    问题?

    【讨论】:

      【解决方案2】:

      您有多种选择,除了标准 C 和 POSIX 库之外,没有一种需要任何库。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-10-30
        • 1970-01-01
        • 2015-01-10
        • 2012-02-23
        • 2019-01-29
        • 2014-06-16
        • 2016-06-19
        • 1970-01-01
        相关资源
        最近更新 更多