setting the execution rate of while loop in a C++ code for real time synchronization
Asked Answered
T

5

8

I am doing a real_time simulation using a .cpp source code. I have to take a sample every 0.2 seconds (200 ms) ... There is a while loop that takes a sample every time step... I want to synchronize the execution of this while loop to get a sample every (200 ms) ... How should I modify the while loop ?

while (1){
          // get a sample every 200 ms
         }
Talkfest answered 18/2, 2013 at 20:49 Comment(4)
How precisely does it need to be 200ms? Is 199.99 - 200.01 or 195-205 acceptable? How much work are you doing other than the make this happen every 200ms?Attitudinarian
Do you do this on a real-time-System or on a normal Computer? It will be quite improbable to get this cycle to exactly 200ms.Inappreciable
You need to tell us your error threshold as well as what OS you're on.Burdened
Use alarm(2) or ualarm(3) (part of POSIX, should be available everywhere) with gettimeofday(2), or perhaps usleep(3) (this is Linux-only, AFAIK). But be careful, what a signal handler is allowed to do is very limited, see signal(7).Bezonian
E
2

what you are asking is tricky, unless you are using a real-time operating system.

However, Boost has a library that supports what you want. (There is, however, no guarantee that you are going to be called exactly every 200ms.

The Boost ASIO library is probably what you are looking for though, here is code from their tutorial:

//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

int main()
{
  boost::asio::io_service io;

  boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));
  t.wait();

  std::cout << "Hello, world!\n";

  return 0;
}

link is here: link to boost asio.

You could take this code, and re-arrange it like this

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

int main()
{
  boost::asio::io_service io;

  while(1)
  {
    boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));

    // process your IO here - not sure how long your IO takes, so you may need to adjust your timer

    t.wait();
  }    

  return 0;
}

There is also a tutorial for handling the IO asynchronously on the next page(s).

Earley answered 18/2, 2013 at 21:0 Comment(0)
S
8

Simple and accurate solution with std::this_thread::sleep_until:

#include "date.h"
#include <chrono>
#include <iostream>
#include <thread>

int
main()
{
    using namespace std::chrono;
    using namespace date;
    auto next = steady_clock::now();
    auto prev = next - 200ms;
    while (true)
    {
        // do stuff
        auto now = steady_clock::now();
        std::cout << round<milliseconds>(now - prev) << '\n';
        prev = now;

        // delay until time to iterate again
        next += 200ms;
        std::this_thread::sleep_until(next);
    }
}

"date.h" isn't needed for the delay part. It is there to provide the round<duration> function (which is now in C++17), and to make it easier to print out durations. This is all under "do stuff", and doesn't matter for the loop delay.

Just get a chrono::time_point, add your delay to it, and sleep until that time_point. Your loop will on average stay true to your delay, as long as your "stuff" takes less time than your delay. No other thread needed. No timer needed. Just <chrono> and sleep_until.

This example just output for me:

200ms
205ms
200ms
195ms
205ms
198ms
202ms
199ms
196ms
203ms
...
Seibel answered 29/3, 2017 at 14:50 Comment(5)
Hi Howard. It looks like the chrono_io.h functionality has been moved to "date.h". So you may remove that header file.Christos
200ms replace with std::chrono::milliseconds(200). So, the line should read . . . next += std::chrono::milliseconds(200)Cheiro
The 200ms syntax is introduced in C++14.Seibel
Just out of curiosity, what if his “stuff” in occasionally one loop takes more than the delay time? Won’t all the remaining loops get affected? Is there a way to solve it?Contactor
If one iteration takes too long, but the average iteration is less than the delay, then the sleep_until returns immediately after the long iteration, and will continue to do so until the average iteration time can drift back onto the schedule. If the average iteration time is greater than the delay, then there is no hope.Seibel
E
2

what you are asking is tricky, unless you are using a real-time operating system.

However, Boost has a library that supports what you want. (There is, however, no guarantee that you are going to be called exactly every 200ms.

The Boost ASIO library is probably what you are looking for though, here is code from their tutorial:

//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

int main()
{
  boost::asio::io_service io;

  boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));
  t.wait();

  std::cout << "Hello, world!\n";

  return 0;
}

link is here: link to boost asio.

You could take this code, and re-arrange it like this

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

int main()
{
  boost::asio::io_service io;

  while(1)
  {
    boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));

    // process your IO here - not sure how long your IO takes, so you may need to adjust your timer

    t.wait();
  }    

  return 0;
}

There is also a tutorial for handling the IO asynchronously on the next page(s).

Earley answered 18/2, 2013 at 21:0 Comment(0)
W
1

The offered answers show you that there are tools available in Boost to help you accomplish this. My late offering illustrates how to use setitimer(), which is a POSIX facility for iterative timers.

You basically need a change like this:

while (1){
          // wait until 200 ms boundary
          // get a sample
         }

With an iterative timer, the fired signal would interrupt any blocked signal call. So, you could just block on something forever. select will do fine for that:

while (1){
          int select_result = select(0, 0, 0, 0, 0);
          assert(select_result < 0 && errno == EINTR);
          // get a sample
         }

To establish an interval timer for every 200 ms, use setitimer(), passing in an appropriate interval. In the code below, we set an interval for 200 ms, where the first one fires 150 ms from now.

struct itimerval it = { { 0, 200000 }, { 0, 150000 } };
if (setitimer(ITIMER_REAL, &it, 0) != 0) {
    perror("setitimer");
    exit(EXIT_FAILURE);
}

Now, you just need to install a signal handler for SIGALRM that does nothing, and the code is complete.

You can follow the link to see the completed example.

If it is possible for multiple signals to be fired during the program execution, then instead of relying on the interrupted system call, it is better to block on something that the SIGALRM handler can wake up in a deterministic way. One possibility is to have the while loop block on read of the read end of a pipe. The signal handler can then write to the write end of that pipe.

void sigalarm_handler (int)
{
    if (write(alarm_pipe[1], "", 1) != 1) {
        char msg[] = "write: failed from sigalarm_handler\n";
        write(2, msg, sizeof(msg)-1);
        abort();
    }
}

Follow the link to see the completed example.

Wizard answered 18/2, 2013 at 22:58 Comment(0)
G
1
#include <thread>
#include <chrono>
#include <iostream>

int main() {
    std::thread timer_thread;
    while (true) {
        timer_thread = std::thread([](){
            std::this_thread::sleep_for (std::chrono::seconds(1));
         });

         // do stuff 
         std::cout << "Hello World!" << std::endl;

         // waits until thread has "slept" 
         timer_thread.join();

         // will loop every second unless the stuff takes longer than that.
    }

    return 0;
}
Gigi answered 29/3, 2017 at 7:23 Comment(2)
Some explanation would be useful as to how your solution solves the question?Glynis
The question was about how to make a piece of code, inside a loop, execute at a regular interval. The simplest implementation is to simply sleep for a certain amount of time, say 200 ms. But if you are doing stuff that takes time, then the next iteration will start latter than that time. The solution is to have a second thread that does the sleeping, without execution, such that it will maintain a constant interval, independent of the code you want to run in the loop.Gigi
A
0

To get absolute percision will be nearly impossible - maybe in embedded systems. However, if you require only an approximate frequency, you can get pretty decent performance with a chrono library such as std::chrono (c++11) or boost::chrono. Like so:

while (1){
    system_clock::time_point now = system_clock::now();
    auto duration = now.time_since_epoch();
    auto start_millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
    //run sample
    now = system_clock::now();
    duration = now.time_since_epoch();
    auto end_millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
    auto sleep_for = max(0, 200 - (end_millis - start_millis ));
    std::this_thread::sleep_for( sleep_for );
}
Arrowood answered 18/2, 2013 at 21:10 Comment(2)
It's better to keep everything in std::chrono types, rather than do a bunch of conversions to primitive types. For a better approach using std::chrono, see this other answer on SO: https://mcmap.net/q/20898/-handling-an-update-loop-using-c-chronoKillerdiller
Instead of calculating the time delta, use std::this_thread::sleep_until(), incrementing the absolute wakeup time by the fixed interval each time.Olfe

© 2022 - 2024 — McMap. All rights reserved.