Skip to main content
6 of 6
Commonmark migration

Linux C++ Timer Class: How can I improve the accuracy?

I wrote this class today, but I am trying to figure out how to make it more accurate. I pass in seconds and multiply by 1000 to make it milliseconds, and the time does not line up. I need the ability to have multiple timers, so using timer_gettime(), which limits one clock per process, is not an option. Also note I borrowed my idea from free glut.

Please feel free to leave general comments on style, but please focus more on how to improve the accuracy of the timer.

#include "TimerManager.h"
#include <iostream>

void func1(int id)
{
  std::cout << "I was called with " << std::endl;
}

void func2(int id)
{
  std::cout << "I was called too ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))" << std::endl;
}

int main(int argc, char *argv[])
{
  TimerManager t;
  t.addTimer(6, func1);
  t.addTimer(2, func2);
  while(true) {
    t.start();
  }
  return 0;
}

#ifndef TIMERMANAGER_H_
#define TIMERMANAGER_H_

#include <stdlib.h>
#include <iostream>
#include <pthread.h>
#include <list>

extern "C" {
  void *create_pthread(void *data);
}

class TimerManager {
public:
  TimerManager();
  ~TimerManager();
  void start();
  void stop();
  void addTimer(long usec, void (*callback)(int id));
private:
  class Timer  
  {
  public:
    Timer(long usec, void (*callback)(int)) :
      duration(usec),
      callback(callback),
      start(0)
    {    
    }   
    bool operator ==(Timer other)
    {   
      if ((this->callback == other.callback) && (this->duration == other.duration)) {
        return true;
      }   
      return false;
    }   
    long duration;
    void (*callback)(int);
    long start;
  };  
  std::list<Timer> m_timers;
  pthread_t m_timer_thread;
  Timer setUpTimer(long usec, void (*callback)(int id));
  friend void *create_pthread(void *data);
  void run();
  bool go; 
};

#endif

#include <algorithm>
#include <iterator>
#include <sys/time.h>
#include "TimerManager.h"

extern "C" void *create_pthread(void *data)
{
  TimerManager *thread_timer_manager = static_cast<TimerManager *>(data);
  thread_timer_manager->run();
  return data;
}

TimerManager::TimerManager() :
    go(false)
{
  int thread_creation = pthread_create(&m_timer_thread, NULL, create_pthread, this);
  if(thread_creation != 0) {
    std::cerr << "Failed to create thread" << std::endl;
    return;
  } else {
    std::cout << "The status flag is " << thread_creation << std::endl;
  }
}

TimerManager::~TimerManager() 
{
}

void TimerManager::run() 
{
  struct timeval l_tv;
  while(true) {
    if (go) {
      typedef std::list<Timer>::iterator li; 
      li begin = m_timers.begin();
      li end = m_timers.end();
      int item = 0;
      for(;begin != end; ++begin) {
        gettimeofday(&l_tv, NULL);
        long elapsed_time = (l_tv.tv_usec - begin->start);
        sleep(1);
        if (elapsed_time >= begin->duration) {
          //std::cout << "Status of timer " << item << " is " << elapsed_time << " > " << begin->duration << std::endl;
          begin->callback(item);
          gettimeofday(&l_tv, NULL);
          begin->start = l_tv.tv_usec;
        } else {
          //std::cout << "Status of timer " << item << " is " << elapsed_time << " < " << begin->duration << std::endl;
        }   
        item++;
      }   
    }   
  }
}

void TimerManager::start()
{
  go = true;
}

oid TimerManager::stop() 
{
  go = false;
}

TimerManager::Timer TimerManager::setUpTimer(long usec, void (*callback)(int id))
{
  struct timeval l_tv;
  gettimeofday(&l_tv, NULL);
  Timer l_t(usec * 100, callback);
  l_t.start = l_tv.tv_usec;
  std::cout << "Timer created with a values of START: " << l_t.start << " CALLBACK: " << &l_t.callback << " DURATION: " << l_t.duration << std::endl;
  return l_t;
}

void TimerManager::addTimer(long usec, void (*callback)(int id))
{
  Timer insert = setUpTimer(usec, callback);
  typedef std::list<Timer>::iterator li;
  li begin = m_timers.begin();
  li end = m_timers.end();
  for(;begin != end; ++begin) {
    if (*begin == *end) {
      return;
    }
  }
  m_timers.push_back(insert);
}

Update: Changes after reading answers.

Here is the updated code,

#include "TimerManager.h"
#include <iostream>
#include <fstream>
#include <sys/time.h>

extern "C"
void func1(int id)
{
  struct timeval l_tv;
  gettimeofday(&l_tv, NULL);
  std::cout << "I was called (1) @ " << l_tv.tv_usec << std::endl;
}

extern "C"
void func2(int id)
{
  struct timeval l_tv;
  gettimeofday(&l_tv, NULL);
  std::cout << "I was called (2) @ " << l_tv.tv_usec << std::endl;
}

int main(int, char *[])
{
  TimerManager t;
  t.addTimer(1000000 / 2, func1);
  t.addTimer(1000000 * 4, func2);
  t.start();
  while(true) {
    sleep(1);
  }
  return 0;
}

#ifndef TIMERMANAGER_H_
#define TIMERMANAGER_H_

#include <stdlib.h>
#include <iostream>
#include <pthread.h>
#include <list>

extern "C" {
  void *create_pthread(void *data);
}

class TimerManager {
public:
  TimerManager();
  ~TimerManager();
  void start();
  void stop();
  void addTimer(long usec, void (*callback)(int id));
private:
  class Timer  
  {
  public:
    Timer(long usec, void (*callback)(int)) :
      duration(usec),
      callback(callback),
      start(0)
    {      
    }
    bool operator ==(Timer other)
    {
      if ((this->callback == other.callback) && (this->duration == other.duration)) {
        return true;
      }
      return false;
    }
    void operator =(Timer other)
    {
      duration = other.duration;
      callback = other.callback;
      start = other.start;
    }
    suseconds_t duration;
    void (*callback)(int);
    suseconds_t start;
  };
  Timer setUpTimer(long micro_duration, void (*callback)(int id));
  friend void *create_pthread(void *data);
  void run();
  bool m_bRunning;
  bool m_bGo;
  long m_lMinSleep;
  std::list<Timer> m_cTimers;
  pthread_t m_tTimerThread;
  pthread_cond_t m_tGoLockCondition;
  pthread_mutex_t m_tGoLock;
};

#endif

#include <algorithm>
#include <iterator>
#include <sys/time.h>
#include "TimerManager.h"

extern "C" void *create_pthread(void *data)
{
  TimerManager *thread_timer_manager = static_cast<TimerManager *>(data);
  thread_timer_manager->run();
  return data;
}

TimerManager::TimerManager() :
  m_bRunning(false),
  m_bGo(false),
  m_lMinSleep(0)
{
  int mutex_creation = pthread_mutex_init(&m_tGoLock, NULL);
  if(mutex_creation != 0) {
    std::cerr << "Failed to create mutex" << std::endl;                                   // Use RAII if resource acquisition fails
    return;
  }

  int mutex_cond_creation = pthread_cond_init(&m_tGoLockCondition, NULL);
  if(mutex_cond_creation != 0) {
    std::cerr << "Failed to create condition mutex" << std::endl;                         // Use RAII if resource acquisition fails
    return;
  }

  int thread_creation = pthread_create(&m_tTimerThread, NULL, create_pthread, this);      // On success call create_pthread to start tiemr loop
  if(thread_creation != 0) {
    std::cerr << "Failed to create thread" << std::endl;                                  // Use RAII if resource acquisition fails
    return;
  }
  m_bRunning = true;
}

TimerManager::~TimerManager() 
{
    pthread_mutex_lock(&m_tGoLock);                                                       // m_bRunning access on other thread
    m_bRunning = false;
    pthread_mutex_unlock(&m_tGoLock);
    void *result;
    pthread_join(m_tTimerThread, &result);                                                // Do not let calling thread exit before deleting
    pthread_mutex_destroy(&m_tGoLock);                                                    // Now destroy the mutex (release resources)
    pthread_cond_destroy(&m_tGoLockCondition);
}

void TimerManager::run() 
{
  pthread_mutex_lock(&m_tGoLock);                                                         // Timers run on seperate thread
  while(m_bRunning) {                                                                     // While timer manager exists
    while (!m_bGo) {                                                                      // While timer manager told to run
      pthread_cond_wait(&m_tGoLockCondition, &m_tGoLock);                                 // Set in the start() member function
    }
    pthread_mutex_unlock(&m_tGoLock);                                                     // Once timer unlocked and mutex released
    if (!m_bRunning) {                                                                    // Make sure timer manager not out of scope
      break;
    }

    struct timeval l_tv;
    usleep(std::max(0l, m_lMinSleep));
    gettimeofday(&l_tv, NULL);                                                            // System call to get time of day
    m_lMinSleep = 0;                                                                      // Used to sleep if no timer to go off soon
    long l_lMin = 0;                                                                      // Used if timer goes off to get actual Min sleep
    for(std::list<Timer>::iterator it = m_cTimers.begin(); it != m_cTimers.end(); ++it) { // Iterate over timers to see which one is going off
      TimerManager::Timer l_oTimer = *it;                                                 // Obtain a copy of the timer
      long elapsed_time = ((l_tv.tv_sec * 1000000 + l_tv.tv_usec) - (l_oTimer.start));    // Calcuate the elapsed time from the start of timer X
      l_lMin = elapsed_time - l_oTimer.duration;                                          // Minimum time you can sleep in loop
      if (elapsed_time >= l_oTimer.duration) {                                            // If time passed is greater than or equal to duration: THEN
        l_lMin = l_oTimer.duration;                                                       // The minimum you can wait is possibly the entire duration of that timer
        l_oTimer.callback(0);                                                             // Call the call back
        gettimeofday(&l_tv, NULL);                                                        // After callback called...
        it->start = (l_tv.tv_sec * 1000000) + l_tv.tv_usec;                               // Start the timer over again
      }
      m_lMinSleep = std::min(m_lMinSleep, l_lMin);                                        // Find the actual minumum time you can sleep to not lock
    }
  }
}

void TimerManager::start()
{
  pthread_mutex_lock(&m_tGoLock);                                                         // Go flag accessed from another thread
  m_bGo = true;
  pthread_cond_signal(&m_tGoLockCondition);
  pthread_mutex_unlock(&m_tGoLock);
}

void TimerManager::stop() 
{
  pthread_mutex_lock(&m_tGoLock);                                                         // Go flag accessed from another thread
  m_bGo = false;
  pthread_mutex_unlock(&m_tGoLock);
}

TimerManager::Timer TimerManager::setUpTimer(long micro_duration, void (*callback)(int id))
{
  struct timeval l_tv;
  gettimeofday(&l_tv, NULL);                                                              // System call to get the ms and sec since Epoch0
  Timer l_oTimer(micro_duration, callback);                                               // Create a timer
  l_oTimer.start = (l_tv.tv_sec * 1000000) + l_tv.tv_usec;                                // Tell the timer when to start
  return l_oTimer;                                                                        // Return a copy to addTimer
}

void TimerManager::addTimer(long usec, void (*callback)(int id)) 
{
  pthread_mutex_lock(&m_tGoLock);                                                         // Tell object to wait till timer inserted
  Timer insert = setUpTimer(usec, callback);
  for (std::list<Timer>::iterator it = m_cTimers.begin(); it != m_cTimers.end(); ++it) {
    if (*it == insert) {                                                                  // Return if timer callback and duration the same
        return;
    }
  }
  m_cTimers.push_back(insert);                                                            // If no duplicate timers found then insert into list
  pthread_mutex_unlock(&m_tGoLock);                                                       // Tell object it is okay to proceed
}
Matthew Hoggan
  • 1.9k
  • 3
  • 20
  • 36