std::this_thread::sleep_for sleeps for too long
In short, the problem is that you use std::this_thread::sleep_for
at all. Or, any kind of "sleep" for that matter. Sleeping to limit the frame rate is just utterly wrong.
The purpose of sleep functionality is, well... I don't know to be honest. There are very few good uses for it at all, and in practically every situation, a different mechanism is better.
What std::this_thread::sleep_for
does (give or take a few lines of sanity tests and error checking) is, it calls the Win32 Sleep
function (or, on a different OS, a different, similar function such as nanosleep
).
Now, what does Sleep
do? It makes a note somewhere in the operating system's little red book that your thread needs to be made ready again at some future time, and then renders your thread not-ready. Being not-ready means simply that your thread is not on the list of candidates to be scheduled for getting CPU time.
Sometimes, eventually, a hardware timer will fire an interrupt. That can be a periodic timer (pre Windows 8) with an embarrassingly bad default resolution, or programmable one-shot interrupt, whatever. You can even adjust that timer's resolution, but doing so is a global thing which greatly increases the number of context switches. Plus, it doesn't solve the actual problem. When the OS handles the interrupt, it looks in its book to see which threads need to be made ready, and it does that.
That, however, is not the same as running your thread. It is merely a candidate for being run again (maybe, some time).
So, there's timer granularity, inaccuracy in your measurement, plus scheduling... which altogether is very, very unsuitable for short, periodic intervals. Also, different Windows versions are known to round differently to the scheduler's granularity.
Solution: Do not sleep. Enable vertical sync, or leave it to the user to enable it.
std::this_thread::sleep_for sleeps shorter than expected in VS2015
The sleep_for()
method implementation in VS2015 already contains a loop around the Sleep()
system call, so any spurious wakeups will not affect it - see _Thrd_sleep()
in VC\crt\src\stl\cthread.c
.
The cause of your problems is most probably the fact that sleep_for()
, and sleep_until()
it calls inside, are using chrono::system_clock
to calculate the time to wait, but you are measuring the period using chrono::steady_clock
.
Both timers might have the same precision, but not necessarily the same accuracy. Then it can happen that the system_clock
is lagging a little bit, while steady_clock
is already a few μs ahead, and the wait calculated using system_clock
is actually shorter than requested.
In this exact case, steady_clock
is implemented using QueryPerformanceCounter()
(see VC\include\chrono
, search for struct steady_clock
), so it will be very accurate and precise, as that's the preferred Windows API to use for exact time measurements.
system_clock
is implemented using GetSystemTimePreciseAsFileTime()
(see VC\include\chrono
, search for struct system_clock
), which promises "the highest possible level of precision (<1us)" also. Alas, as the MSDN page says, this API is supported only from Windows 8 up and you are stuck with the older GetSystemTimeAsFileTime()
with just ms precision on your Windows 7 machine. And that is most probably the result of your measurement errors.
Depending on your exact use case, you might deal with this in different ways of course. I'd consider just ignoring the small error, as suspending the thread and waiting for the scheduler to wake it up will not be very accurate anyway.
this_thread::sleep_for sleeps early
printf doesnt necessarily flush the buffer, which is why you dont see it until the thread resumes from sleep (it does this because it notices the thread is inactive). To force a flush add fflush(stdout);
before the call to thread sleep.
Unpredictable behaviour of std::sleep_for on Windows 10
This answer is created from Jeremy Friesner's comment. He suggests to try timeBeginPeriod(1)
at the beginning of the main()
. That worked out! I created a modification of the program, and it runs 10 times faster.
Here is the code:
#include <thread>
#include <iostream>
#include <chrono>
#include <windows.h>
using namespace std;
int main(int argc, char const *argv[])
{
timeBeginPeriod(1);
unsigned t = 0;
auto start = ::std::chrono::steady_clock::now();
for(unsigned i = 0; i < 100; ++i) {
::std::this_thread::sleep_for(chrono::milliseconds(1));
t ^= i; // prevent overeager optimization
}
auto stop = ::std::chrono::steady_clock::now();
auto elapsed = ::std::chrono::duration_cast<::std::chrono::milliseconds>(stop - start);
::std::cout << elapsed.count() << "\n";
::std::cerr << t << "\n";
return 0;
}
It can be compiled using the following command:
cl /EHsc /std:c++17 /O2 sleep_for.cpp winmm.lib
Thanks, Jeremy!
P.S.
Increasing timer frequency has its price:
https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/
boost::this_thread::sleep_for sleeping entire program
it hangs there and sleeps all the other threads as well
No it doesn't. If it seems that way, that is because the other threads were already stuck or finished.
Look for things that block (mutex.lock, condition wait, IO operations, etc.) or check that the threads didn't exit.
Notes
Your
seconds
calculations is off. On my system, the following:Live On Coliru
#include <boost/chrono.hpp>
#include <iostream>
int main() {
std::cout << boost::chrono::duration<long, boost::ratio<60> >(1)/boost::chrono::seconds(1) << std::endl;
}Prints
60
So, what you named
seconds
is actually minutes. Just do this instead:using boost::chrono::seconds;
int delay = std::strtoul(getId().c_str(), NULL, 10)*10;
sleep_for(seconds(delay));Your random delay is only random if the
getId
return is. Usingboost/random.hpp
you can make it truly random, with good range control. E.g. to sleep between 1'000 and 3'000 ms:int random_gen(int low, int high) { // not threadsafe
static boost::random_device rdev;
static boost::mt19937 prng(rdev);
return boost::uniform_int<>(low, high)(prng);
}void Upload::Wait() {
int const ms_delay = random_gen(1000, 3000);
cout << "RANDOM WAIT TIME = " << ms_delay << endl;
sleep_for(milliseconds(ms_delay));
}Note to seed using
random_device
as shown (so true random seed) you need to link the random library. Otherwise, you can "stoop" to a time-based seed:static boost::mt19937 prng(std::time(NULL));
Here's a self-contained version of your code with the various suggestions applied, demonstrating that there is no deadlock/softlock:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/chrono.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <boost/random.hpp>
using boost::this_thread::sleep_for;
using boost::chrono::seconds;
using boost::chrono::milliseconds;
using boost::lexical_cast;
using std::cout;
using std::endl;
struct Upload {
std::string getId() const { return "42"; }
void Wait();
void ReadFileNames();
};
void Upload::ReadFileNames() {
cout << "[DEBUG] ReadFileNames -> A " << endl;
Wait();
cout << "[DEBUG] ReadFileNames -> B " << endl;
// read filename stuff
}
int random_gen(int low, int high) { // not threadsafe
static boost::mt19937 prng(std::time(NULL));
return boost::uniform_int<>(low, high)(prng);
}
void Upload::Wait() {
int const ms_delay = random_gen(1000, 3000);
cout << "RANDOM WAIT TIME = " << ms_delay << endl;
sleep_for(milliseconds(ms_delay));
}
void background(char const* name) {
// desync different background threads
sleep_for(milliseconds(boost::hash_value(name) % 1000));
for (int i=0; i<5; ++i) {
sleep_for(seconds(1));
std::clog << name << " " << i << std::endl;
}
}
void threadAfn() { background("thread A"); }
void threadBfn() { background("thread B"); }
void Uploadfn() {
Upload u;
u.ReadFileNames();
}
int main() {
boost::thread threadA(threadAfn);
boost::thread threadB(threadBfn);
boost::thread Upload(Uploadfn);
threadA.join();
threadB.join();
Upload.join();
}
Prints, e.g.:
[DEBUG] ReadFileNames -> A
RANDOM WAIT TIME = 1150
[DEBUG] ReadFileNames -> B
thread A 0
thread B 0
thread A 1
thread B 1
thread A 2
thread B 2
thread A 3
thread B 3
thread A 4
thread B 4
Related Topics
_Iterator_Debug_Level Value '0' Doesn't Match Value '2'
What Encoding Does Std::String.C_Str() Use
Detect Gcc Compile-Time Flags of a Binary
Undefined Reference to Winmain (C++ Mingw)
Is It Bad Practice to Allocate Memory in a Dll and Give a Pointer to It to a Client App
How to Read from a Text File, Character by Character in C++
Why Doesn't Delete Destroy Anything
Function to Mangle/Demangle Functions
Boost.Python Not Supporting Parallelism
How to Get a Color Palette from an Image Using Opencv
How to Do Input Validation in C++ with Cin
C++ Boost: What's the Cause of This Warning
How to Build Google's Protobuf in Windows Using Mingw
Opencv How to Select a Region of Image Irregularly with Mouse Event? C/C++