简体   繁体   中英

C++ - Loop with fixed delta time on a background POSIX Thread

The goal is to call a function on a background thread with a fixed delta Time.

The function should get called 60 times / second, hence at timestamps 0, 0.0166, etc. The timestamps should be hit as precisely as possible.

The simple but probably not best solution would be to run a while(true)-loop and let the thread sleep until the next time the function should be called. Here's half C++ / half pseudo-Code how it'd do it.

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

Yesterday, I asked this very same question asking for a solution using C++11 std::thread. Some people in the comments told me that using POSIX thread would be better. Though pthreads seem even more complicated to me, so I hope somebody here can show me how to solve this issue with pthreads.

Posix Threads surely grant faster communication among foreground and background tasks, but if you want precision I would suggest using clock_nanosleep to keep track of time on top of a real time kernel.

You can use the following base functions to shape your task life-cycle:

#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

Here you have the implementation file:

#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;
}

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

The background-task should basically do the following:

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

        /* do your periodic task */

    }
}

where offset is the initial time you wait since when you start the task, and period is the amount of time you wait in-between one invocation of the task and another.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM