Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost deadline_timer minimal example: should I substitute "sleep"?

Tags:

c++

boost

I have a thread where I need to do something every 10 ms. So I have very simple code, like that:

while (work) {
    // do something
    Sleep(10000); // boost sleep can be also used
}

I heard that Sleep is not recommended in general and if I substitute it with deadline_timer overall application performance will be better, in particular I will avoid expensive "context switch".

Should I change sleep to deadline_timer and if so can someone give an example?

like image 281
Oleg Vazhnev Avatar asked May 03 '13 08:05

Oleg Vazhnev


2 Answers

It all depends on the requirements for the 10ms.


10ms delay between iterations

If an application needs a 10ms delay between iterations, then sleep is fine. Assuming work() takes 7 milliseconds to complete, the timeline would result in the following:

 Time  | Action
-------+------------
0.000s | begin work
0.007s | finish work, block
0.017s | finish blocking, begin work
0.024s | finish work, block
0.034s | finish blocking, begin work

It may be worth considering using Boost.Thread's this_thread::sleep_for() for readability:

#include <boost/thread.hpp>

int main()
{
  for (;;)
  {
    work();
    boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
  }
}

10ms max delay between iterations

If the max delay between iterations is 10ms, then the time spent executing work needs to be reduced from the 10ms delay. Assuming work() takes 7 milliseconds to complete, the timeline would result in the following:

 Time  | Action
-------+------------
0.000s | begin work
0.007s | finish work, block
0.010s | finish blocking, begin work
0.017s | finish work, block
0.020s | finish blocking, begin work

The using a timer synchronously tutorial can be a good place to start. One point to consider is that Boost.Asio provides a few timers. If the 10ms delays should not be affected by changes to the system clock, then a consider using steady_timer. Otherwise, deadline_timer should be fine.

#include <boost/asio/steady_timer.hpp>

boost::asio::io_service io_service;
boost::asio::steady_timer timer(io_service);

int main()
{
  for (;;)
  {
    timer.expires_from_now(boost::chrono::milliseconds(10));
    work();
    timer.wait();
  }
}

Another consideration is that if work() takes 13 milliseconds to complete, then there will be no delay between work, as the max delay has been exceeded. However, this results in work() being done every 13 milliseconds, rather than work() being done every 10 milliseconds.

 Time  | Action
-------+------------
0.000s | begin work
0.013s | finish work, block
0.013s | finish blocking, begin work
0.026s | finish work, block
0.039s | finish blocking, begin work

Perform work every 10ms

If the time it takes to complete work() exceeds the delay, then work() will not be done every 10ms. To accomplish this, multiple threads may need to be used. The following is a timeline with 2 threads asynchronously performing work that is scheduled every 10 milliseconds, but takes 13 milliseconds to complete:

 Time  | Thread A                   | Thread B
-------+----------------------------+---------------------------
0.000s | schedule work, begin work  |
0.010s |                            | schedule work, begin work 
0.013s | finish work, block         |
0.020s | schedule work, begin work  |
0.023s |                            | finish work, block
0.030s |                            | schedule work, begin work
0.033s | finish work, block         |

The using a timer asynchronously may provide a basic introduction. The overall idea is to add work into the io_service, and every 10 milliseconds a thread that is running the io_service will be selected to invoke work(). The thread pool size can be increased or decreased based on the amount of time work() takes to complete. In the case where work takes 7 milliseconds, then a single thread could asynchronously wait on the timer.

#include <boost/asio/steady_timer.hpp>

boost::asio::io_service io_service;
boost::asio::steady_timer timer(io_service);

void handle_timer(const boost::system::error_code& error);

void schedule_work()
{
  // Schedule more work.
  timer.expires_from_now(boost::chrono::milliseconds(10));
  timer.async_wait(&handle_timer);
}

void handle_timer(const boost::system::error_code& error)
{
  if (error) return;
  schedule_work();
  work();
}

int main()
{
  // Add work to io_service.
  schedule_work();

  // Create 2 threads that will run io_service.
  boost::thread_group threads;
  for (std::size_t i = 0; i < 2; ++i)
    threads.create_thread(boost::bind(
      &boost::asio::io_service::run, &io_service));

  // Wait for threads to finish.
  threads.join_all();
}

When introducing concurrency to meet the deadline, verify that work() is thread-safe.

like image 146
Tanner Sansbury Avatar answered Nov 10 '22 20:11

Tanner Sansbury


Using Sleep() is OK. I assume it's the Windows API function, which takes milliseconds anyway, so you should probably pass 10 instead of 10000 if you want 10 ms.

The biggest issue in a simple program using sleep like this may be drift. If the interval needs to be quite precise, you'll face some challenges. You didn't say whether you care about what happens if your actual logic takes a few milliseconds to complete--you could start the next iteration 10 ms later, or less to "catch up."

like image 26
John Zwinck Avatar answered Nov 10 '22 21:11

John Zwinck