【问题标题】:C++ - Loop with fixed delta time on a background POSIX ThreadC++ - 在后台 POSIX 线程上具有固定增量时间的循环
【发布时间】:2016-06-25 09:44:36
【问题描述】:

目标是在具有固定增量时间的后台线程上调用函数。

该函数应该每秒调用 60 次,因此在时间戳 0、0.0166 等处。应该尽可能精确地命中时间戳。

简单但可能不是最好的解决方案是运行一个 while(true)-loop 并让线程休眠,直到下一次调用该函数。这是一半的 C++ / 一半的伪代码如何做到这一点。

float fixedDeltaTime = 1.0 / 60.0;
void loopFunction() 
{
      while(true)
      {
         auto currentTime = getTime();
         // do work
         auto timePassed = getTime() - currentTime;
         int times = (timePassed / fixedDeltaTime);
         sleep(  (fixedDeltaTime * times) - timePassed)
      }
}

int main()
{
   std::thread loopFunction(call_from_thread);
   return 0;
}

昨天,我问了同样的问题,询问使用 C++11 std::thread 的解决方案。 cmets 中的一些人告诉我,使用 POSIX 线程会更好。虽然 pthread 对我来说似乎更复杂,所以我希望这里有人能告诉我如何用 pthread 解决这个问题。

【问题讨论】:

  • 您当前的解决方案是否不够好?
  • 嗯,是的。 std::sleep_for 不是很准确,所以我必须以非常小的间隔进行,并检查调用 loopFunction() 的时间是否已经到来。在另一个线程中,人们还说计时时间测量非常昂贵。
  • @keyboard - 既然我提到计时器可能很昂贵,那么实际使用的增量时间是多少?如果它真的是 0.0166,请不要介意我对计时器性能的评论(除非您要启动 100 个并行线程)。同样,如果您真的想要性能,请在每个平台上使用本机可等待计时器,但我认为对于如此大的超时来说这太过分了。
  • 如果你想要 非常 准确的计时而不编写内核级代码,你可能必须将一个核心专用于工作人员,让它在没有等待/睡眠的情况下运行,并使用性能计数器来跟踪 ns 或 ms 以在需要时触发...
  • 好吧,我现在使用 1.0 / 600.0 秒的间隔,我只需要 1 个线程。但我问的不仅仅是因为你提到了性能。这也是关于学习如何使用 pthread 进行操作。

标签: c++ multithreading c++11 pthreads posix


【解决方案1】:

Posix 线程确实可以在前台和后台任务之间提供更快的通信,但如果您想要 精确度,我建议您使用 clock_nanosleep 来实时跟踪时间内核。

您可以使用以下基本功能来塑造您的任务生命周期:

#ifndef __TIMERS_H__
#define __TIMERS_H__

#include <stdint.h>  /* uint64_t                           */
#include <time.h>    /* clockid_t of clock_nanosleep()     */

/*--------------------------------------------------------------------------------------*/

/* Common facility functions needed for
 * high precision timers usage
 */

/*--------------------------------------------------------------------------------------*/

#define SEC_VAL 1000000000ULL

enum return_values {
    RETURN_FAILURE = 0,
    RETURN_SUCCESS = 1,
    RETURN_EMPTY = 2
};

typedef void* timespec_ptr;

typedef struct timespec timespec_t;

/* Adds time_us microseconds to timer ts
 */
void timespec_add_ns(timespec_ptr ts,
        uint64_t time_ns);
/* Makes the thread wait for the next activation of the timer ts
 */
void wait_next_activation(timespec_ptr ts); /*** @ Tasks ***/
/* Starts the periodic timer
 */
int start_periodic_timer(timespec_ptr ts,
        uint64_t init_offs_ns);

/* Computes the difference among two clocks
 */
long calcdiff(struct timespec t1,
        struct timespec t2);

/*--------------------------------------------------------------------------------------*/

extern int clock_nanosleep(clockid_t clock_id, int flags,
                           const struct timespec* request,
                           struct timespec* remain);

/*--------------------------------------------------------------------------------------*/

#endif

这里有实现文件:

#include "timers.h"
#include <stdio.h> /* fprintf */

/*--------------------------------------------------------------------------------------*/

/* Adds time_us microseconds to timer ts
 */
void timespec_add_ns(timespec_ptr ts,
        uint64_t time_ns)
{
    if (ts)
    {
        timespec_t* ts_ = (timespec_t*) ts;

        time_ns += ts_->tv_nsec;
        ts_->tv_sec += time_ns/SEC_VAL;
        ts_->tv_nsec = time_ns%SEC_VAL;
    } else {
        fprintf(stderr, "Warning (%s): input argument is NULL, \
                         request ignored.\n", __FUNCTION__);
    }
}

/* Makes the thread wait for the next activation of the timer ts
 */
void wait_next_activation(timespec_ptr ts)
{
    if (ts)
    {
        timespec_t* ts_ = (timespec_t*) ts;

        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ts_, NULL);
    } else {
        fprintf(stderr, "Warning (%s): input parameter is NULL, \
                         request ignored.\n", __FUNCTION__);
    }
}

/* Starts the periodic timer
 */
int start_periodic_timer(timespec_ptr ts,
        uint64_t init_offs_ns) /*** @ Tasks ***/
{
    if (ts)
    {
        timespec_t* ts_ = (timespec_t*) ts;

        clock_gettime(CLOCK_MONOTONIC, ts_);
        timespec_add_ns(ts, init_offs_ns);
        return RETURN_SUCCESS;
    } else {
        fprintf(stderr, "Warning (%s): input parameter is NULL, \
                         request ignored.\n", __FUNCTION__);
        return RETURN_FAILURE;
    }
}

/* Computes the difference among two clocks
 */
long calcdiff(struct timespec t1,
        struct timespec t2) /*** @ Tasks ***/
{
    long diff;

    diff = SEC_VAL * ((int) t1.tv_sec - (int) t2.tv_sec);
    diff += ((int) t1.tv_nsec - (int) t2.tv_nsec);
    return diff;
}

/*--------------------------------------------------------------------------------------*/

后台任务基本上应该做到以下几点:

void run (void *args) {
    start_periodict_timer(&timer_, offset);
    while (true) {
        wait_next_activation(&timer_);
        timespec_add_ns(&timer_, period);

        /* do your periodic task */

    }
}

其中 offset 是您从启动任务开始等待的初始时间,而 period 是您在两次调用任务之间等待的时间另一个。

【讨论】:

  • 谢谢,不过好像不能用这个。我没有声明/找到clockid_t(以及更多)。显然这些东西在 iOS 和 OSX 上是不存在的:stackoverflow.com/questions/13472761/…
  • @keyboard 感谢您强调此限制。您是否尝试过在 iOS 上使用 nanosleep()/usleep()(如果可用)?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-10
  • 2015-05-04
  • 2015-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多